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 and myself, came up with Reform – a framework-agnostic, anti-database form object.

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.

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.

Rendering.

This simple trick makes it super-easy to use that form with Rails’ form helpers, 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.

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.

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]
  )

Why Another Form Object?

I read several blog posts 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 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 based mapper, make your own. It’s just 100 lines of code so far. Give it a try!

24 Responses to “Reform – Decouple Your Forms From Your Models!”

  1. This looks awesome. I agree both strong params and attr accessie are poor highly coupled solutions. Thanks for this I am looking forward to trying it out.

  2. Thanks, Nick. I can’t wait to try it!

  3. The link to the Github repo is broken. Url is: https://github.com/apotonick/reform


  4. MrChris

    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.


  5. MrChris

    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.

  6. 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.

    https://gist.github.com/gogogarrett/5544359

  7. Just a quick question – does it work correctly with unique validations?


  8. sengming

    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.

  9. 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.

  10. 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.


  11. nick

    Hooray, we’re on Ruby5! Thanks!


  12. nick

    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.


  13. nick

    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?

  14. 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”.


  15. nick

    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.

  16. nick: Vecause you are trying to instantiate an object when you are entering stuff into a form.

  17. Nice one Nick. Definitely going to use this.

  18. What about timestamp attributes?

    I implemented such objects using Virtus (https://github.com/solnic/virtus) as suggested at http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/ but I found it very difficult to handle timestamp attributes.


  19. phillip

    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


  20. nick

    Giovanni: Is that what you’re after?


  21. Dantas

    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


  22. nick

    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!


  23. aldo

    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.


  24. nick

    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.

Leave a Reply