Reform – Decouple Your Forms From Your Models!

{{{
To cut a long story short: Working on a nested form we decided not to go with @nested_attributes@. In fact, we wanted a form with validations, rendering and processing completely decoupled from the model. I mean, why would a form wanna know about the database layout?

So we, that is one of my lovely co-workers “Garrett Heinlen”:https://twitter.com/GoGoGarrett and myself, came up with “Reform – a framework-agnostic, anti-database form object”:https://github.com/apotonick/reform.

h3. What Does It Look Like?

Let’s assume for now our form was to create a song request for a radio station – for whatever reason this implies creating both a row in the @songs@ and in the @artists@ table. We use an ActiveRecord example here, however, reform is not database-dependent at all.

class SongRequestForm < Reform::Form
  include DSL

  property :title,  on: :song
  property :name,   on: :artist

  validates :name, :title, presence: true
end

As you can see, this is pretty straight-forward. Define the layout and throw in some validations.

h3. What Is The Difference?

The form doesn’t know anything about your database. All you do is specifying what fields you want and where to map those. Also, note we’re using @ActiveModel@’s validations in the class and thus have per-form validations that don’t get in the way in our models.

For a better understanding, here’s how you would instantiate your reform.

@form = SongRequestForm.new(song:   Song.new, 
                             artist: Artist.new)

This would usually happen in your controller action.

Basically, from the key-object hash Reform will create a @Composition@ object that simply delegates accessors to the underlying objects. How does it know how to do that? Well, you just defined it!

  property :title,  on: :song

Calls to @#title@ or @#title=@ will be delegated.

  @form.title #=> @form.song.title

Note that this doesn’t involve any database-magic – it is simple delegation based on your specification.

h3. Rendering.

This simple trick makes it super-easy to use that form with Rails’ form helpers, “simple_form”:https://github.com/plataformatec/simple_form or whatever form rendering you fancy.

= simple_form_for @form do |f|

  = f.input :name
  = f.input :title

I don’t cover the details here but the form object will even expose the necessary @ActiveModel@-compliant methods when “you tell it to do so”:https://github.com/apotonick/reform#simple-integration.

h3. Processing Evil Input.

Now how do we update our form with incoming data, process it, validate it and display possible errors?

if @form.validate(params[:song_request])

The form comes with a @#validate@ method which accepts a hash of data. Validations you defined will be run and @form.errors@ will return potential errors from your confusing input.

Speaking of confusing: You no longer need @attr_accessible@ or @strong_parameters@ anymore. Why is that? Well, again, you defined the form layout earlier!!! Therefore, the form knows its fields and will simply ignore unsolicited input.

Another sweet feature is that the form will display the user’s input in the fields after a validation – without even touching your models.

@form.title => "Sunlit Nights" # from model.
@form.validate(title: "Scarified")
@form.title => "Scarified" # from user input.

h3. Saving Safe Input.

When you decided that the input is alright you can let Reform save the data.

if @form.validate(title: "Scarified")
  @form.save # @form.song.title = "Scarified"

It will pass the incoming data to the respective models. This is nice, but often you want more control. That’s where a block to @#save@ kicks in.

@form.save do |data, nested|
  data.title #=> "Scarified"
  nested[:song] #=> {title: "Scarified}

The block yields two handy arguments. The first @data@ is a plain list of the incoming and validated data (currently implemented as an @OpenStruct@ instance).

The second @nested@ is a hash keyed following your mapping instructions from earlier – remember, the calls to @::property@?

Makes it really easy to use ActiveRecord’s magic without the horror.

@form.save do |data, nested|
  Song.find(params[:id]).update(
    nested[:song]
  )

h3. Why Another Form Object?

I read several “blog posts”:http://pivotallabs.com/form-backing-objects-for-fun-and-profit/ that were really inspiring, however, they were either implementing a create form or an edit form, only. Also, we didn’t like the hard-core ActiveRecord wiring.

Reform gives you all the goodies like validation, parameter filtering and even rendering in a dedicated class. That keeps your UI logic where it should be. Hell yes, forms _are_ part of your UI and shouldn’t be configured in your database models.

In addition to that, “Reform”:https://github.com/apotonick/reform gives you all the flexibility to change internals. If you don’t like the automatically created @Composition@ object, use your own. If you don’t like the “representable”:https://github.com/apotonick/representable/ based mapper, make your own. It’s just 100 lines of code so far. Give it a try!

}}}

Advertisement

24 thoughts on “Reform – Decouple Your Forms From Your Models!

  1. I’ve started using Form objects recently and they work great. Much better than the horror that is accepts_nested_attributes_for.

    Next time I need a Form object I’ll try reform.

    Like

  2. In the examples in the github documentation for this gem, you build and create new objects within the save block. I’d rather do that kind of behaviour in the form object itself rather than the controller. It makes testing the object easier, also I like to keep my controllers fairly dumb.

    Like

  3. Hello MrChris,

    In the application I am currently using Reform, and I am using workflow objects to structure saving and calling services in my own manner. Reform simply gives you a simple API that you can wire your applications needs.

    Here is a simple example of the workflow objects I’m talking about.


    class ArtistsController < ApplicationController
    def create
    @form = create_new_form
    workflow = Workflows::ArtistWorkflow.new(@form, params[:artist])
    workflow.process do |obj|
    return respond_with obj
    end
    render :new
    end
    def update
    @form = create_edit_form
    workflow = Workflows::ArtistWorkflow.new(@form, params[:artist])
    workflow.process do |obj|
    return respond_with obj
    end
    render :edit
    end
    private
    def create_new_form
    ArtistForm.new(artist: Artist.new, song: StudentProfile.new)
    end
    def create_edit_form
    artist = Artist.find(params[:id])
    ArtistForm.new(artist: artist, song: artist.song)
    end
    end


    module Workflows
    class ArtistWorkflow
    attr_reader :form, :params
    def initialize(form, params)
    @form = form
    @params = params
    end
    def process
    if form.validate(params)
    form.save do |data, map|
    if form.student.new_record?
    new_artist = Services::CreateArtistWithSong(map[:student], map[:song]).call
    NotifyArtistCreation(new_artist).call
    else
    new_artist = Service::ManageArtistWithSong.new(map[:student], map[:profile]).update(form.artist.id, form.song.id)
    end
    yield new_artist if block_given?
    end
    end
    end
    end
    end

    Like

  4. Nice workflow pattern there Garrett,

    I do see where MrChris is coming from though. I love how you guys already have a great DSL for validation. Wish there was also a way to specify how you save it on the form DSL.

    Like

  5. Brilliant work! Have you thought about inheriting constraints from the database?
    For example what about checking that a value is the correct type? If I have a data store that expects a number for some attribute but I get word characters, I’d rather not have to specify all that.
    Even without that, this looks great.

    Like

  6. Good article, but I have to correct one thing, and most people seem to be in this line of thinking that is wrong.

    Models have nothing, absolutely nothing, to do with the database layer. The fact that AR models inherit from AR:Base is for convenience.

    Your model is your data structure and your business logic, not persistence.

    Like

  7. cpuguy83: I totally agree with what you say – let’s put it this way: the term model is terribly misused in Rails. We mix everything, business, form validations, persistence, into AR. And that is the actual problem, when someone speaks about his “model” I don’t know if they’re refering to the persistence, the domain logic or whatever else.

    Like

  8. Jim: Hey buddy! How are you? 😉 So, do you mean something like this:

    class CommentForm < Reform::Form
      include Reform::ActiveRecord
    
      model Comment
    end
    

    And the mixin would then use inflections or whatever to retrieve the properties and the validations?

    Like

  9. nick: The validations aren’t form validations, they are instantiation validations which are basically saying “this instance needs at least have these properties to function properly”.

    Like

  10. teamon: Uniqueness validations can now be done using the Reform::Form::ActiveRecord module.

    cpuguy83: Yeah, right. But why are those “instantiaton validations” used for forms in Rails, then? Reform validations are dedicated form validations.

    Like

  11. Looks very useful!

    But what still isn’t clear to me: how do you validate database data consistency? There are many ways you write to the database, user input from a form is just one.

    If you still do it in the model/DAO, do you duplicate the validations; if not, which validation do you do where, and how do you deal with inconsistencies between form and DAO validations?

    Phillip

    Like

  12. Awesome.

    So using this pattern the ARecord will handle only associations and persistence. All business validations will be placed in the Workflow/Interactor. right ?

    Lets suppose that the user can only make a comment after 8PM.

    Looking for your reform_example, I think that you will put in the Workflow Object.am I right ? How do you bubble up this erros up ?
    or the business validations will be placed in the Form ?

    Thnaks, good job

    Like

  13. Dantes: Hiding a form based on a time rule is something I’d put in an upper layer, not into the form itself. However, if the form should still show up but only process data in a certain time frame, that could go into the form as a validation. Actually, this is an interesting question, thanks!

    Phillip: We are working on making reform more flexible so you can basically use a form even in your business layer as a validation instance. This would allow to completely move validations into “validation objects” (forms). Another approach would be to dynamically “copy” validations from AR to a form.

    I’ll keep you posted!

    Like

  14. hi, I did not understand why I should insert validations on form model instead of letting them in the original AR model .
    I have complex validation and my AR model and for me it is difficult to fit them into form model, In addition to this I need to populate my AR model also through other routes that are not exclusively the form model.

    Like

  15. aldo: In many cases, the validations for your UI differ from your model’s direct API, e.g. a password_confirmation field shouldn’t be part of the “every-day API”.

    The problem in AR is that the model provides several different APIs – Reform changes that by providing a new abstraction layer that is exclusively reflecting the UI and not the “every-day API”. That’s why you push validations into the form.

    I totally see the point about duplicated validations, thou, and we’re working on a solution for that.

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s