{{{
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!
}}}
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.
LikeLike
Thanks, Nick. I can’t wait to try it!
LikeLike
The link to the Github repo is broken. Url is: https://github.com/apotonick/reform
LikeLike
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.
LikeLike
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.
LikeLike
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
artist_controller.rb
hosted with ❤ by GitHub
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
artist_workflow.rb
hosted with ❤ by GitHub
LikeLike
Just a quick question – does it work correctly with unique validations?
LikeLike
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.
LikeLike
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.
LikeLike
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.
LikeLike
Hooray, we’re on Ruby5! Thanks!
LikeLike
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.
LikeLike
Jim: Hey buddy! How are you? 😉 So, do you mean something like this:
And the mixin would then use inflections or whatever to retrieve the properties and the validations?
LikeLike
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”.
LikeLike
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.
LikeLike
nick: Vecause you are trying to instantiate an object when you are entering stuff into a form.
LikeLike
Nice one Nick. Definitely going to use this.
LikeLike
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.
LikeLike
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
LikeLike
Giovanni: Is that what you’re after?
LikeLike
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
LikeLike
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!
LikeLike
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.
LikeLike
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.
LikeLike