The new Reform 1.1 release brings several nice improvements and lots of fixes to the form object gem. This release is just a transitional one, the 2.0 version is gonna move huge chunks of code into a separate gem. Let me talk about this in a minute.
Non-Intrusive Validation.
Reform comes with a mechanism to populate your object graph by deserialising the incoming form data to objects.
class AlbumForm < Reform::Form collection :songs, populate_if_empty: Song do property :title end end
This would create Song
instances where they were missing in the album.
AlbumForm.new(album).validate( "songs" => [ {"title": "Eat Your Words"} ] )
In former versions of Reform, just by calling #validate
Reform would already attach those new songs to the album model – by pushing them via songs[]=
.
This created confusion. And is different now. The built-in populators do not touch the model at all. This only happens when saving or syncing the form.
Coercion Simplified.
Reform allows you to use virtus coercion.
class AlbumForm < Reform::Form property :released_at, type: DateTime end
From 1.1 onwards, coercion only happens in #validate
– any other API method will not trigger coercion. This makes the workflow predictable as we had problems with coercion happening where it shouldn’t.
Manual Coercion.
You can also implement your own filtering by overriding the setter for a property.
class AlbumForm < Reform::Form property :released_at def released_at=(v) super DateTime.parse(v) end end
Note that you can use super
to call the original setter but still provide your own code for filtering or sanitising or whatever fun stuff you’re after.
Overriding In Nested Forms.
This brings me to the next improvement. The above example only worked for top-level properties. That sucked and is fixed now, allowing you stuff as follows.
class AlbumForm < Reform::Form collection :songs do property :written_at def written_at=(v) super DateTime.parse(v) end end end
Methods defined in nested forms actually extend that nested form class – as it should be!
Forms In Modules.
This is my favourite improvement as it maximises the reusability of forms: you can now define forms along with properties, validations and accessors/helpers in modules!
module SongsForm include Reform::Form::Module collection :songs property :title validates :title, presence: true end end
This module can now be included into real forms.
class AlbumForm < Reform::Form property :title include SongsForm end
That should help to keep forms DRY, as it is a common pattern to have several different forms for the same model with lots of shared functionality.
Inheritance Improved.
Reform 1.1 uses representable 2.0 internally for all kinds of mappings and declarations. This actually allows you to “fine-tune” forms and overriding or extending properties that were inherited.
class AlbumForm < Reform::Form property :title include SongsForm collection :songs, inherit: true do property :artist end end
This will extend the existing, inherited songs
form and add the artist
property. Read the docs for in-depth information about this or sign up for my upcoming book which discusses this pattern in detail.
Validations From Models.
While Reform/Trailblazer encourages you to have empty models that only configure your persistence layer, Reform now allows copying validations from models. This way you can quickly set up forms without having to write redundant validations.
class SongForm < Reform::Form property :title extend ActiveModel::ModelValidations copy_validations_from Song end
Thanks to Cameron Martin for his excellent work.
Deserialise JSON.
A simple change in Reform now allows forms to deserialise JSON, XML and YAML besides the original support for hashes. While this might sound weird, this little improvement actually allows forms to become part of your HTTP API.
This is an integral part of Trailblazer: Here, every domain action is encapsulated in a so called “Operation” which internally uses a form object to deserialise incoming data, setup the object graph and validate the application state.
By making forms do JSON and friends, too, they can be used for normal web forms, console/model API and HTTP APIs, which is pretty awesome.
Again, this is all in my book but you can have a sneak peek in the Traiblazer example app gemgem – thanks to @GoGoGarrett for that name, BTW 🙂
class AlbumForm < Reform::Form representer_class.send :include, Representable::JSON def deserialize_method :from_json end property :title include SongsForm collection :songs, inherit: true do property :artist end end
This rather clumsy extension (to be improved!) allows to call #validate
with JSON.
AlbumForm.new(album). validate('"songs": [{"title": "Eat ..')
The API is not yet finalised, I just wanted to give you an out-sight on where this is going.
Reform 2.0 coming.
Speaking of out-sights: Reform 2.0 is already on its way. A major improvement here will be to move all the heavy model lifting (populating, syncing, saving, etc) into disposable which will be a model abstraction layer in Trailblazer (or without Trb).
Use it, and let me know what you think!
Will the Modules work in forms with compositions? Currently all my forms (even when using a single model) uses composition to keep a default workflow behavior.
I have 3 forms that handles the same class, I already included the validations in a concern but I need to keep the same properties on all forms.
“`
properties [
:name,
:address,
…
], on: :pessoa
“`
Can I move that into a module, with the validations?
LikeLike
Jeff: Yes, we can. Composition is completely decoupled from the declarative layer of Reform.
LikeLike
s/populate_if_emtpy/populate_if_empty :*
LikeLike
Piotr: AAAAaaah I keep spelling it wrong!!! :*
LikeLike