Today I released Reform 1.2 – the bug-free edition™. I am extremely excited about it. This release doesn’t break any existing code (hence the minor bump) but brings a bunch of new features that I already use all across my apps.
As always, a complete list of the CHANGES can be found in the CHANGES file.
Non-ActiveModel Models
Reform has been supporting non-ActiveRecord objects (“POROs”) ever since: this was one of the reasons we wrote it. However, in Rails apps we automatically included methods to help the Rails form builder infer field types. This didn’t go well if your model wasn’t an ActiveRecord one.
To allow form helpers like simple_form to access your form’s model for type interrogation you need to activate it manually now.
class SongForm < Reform::Form include ModelReflections
Now a TEXT
column will be displayed as a textarea
, and so on.
Skipping Deserialisation
Often a form needs to skip or ignore data from an incoming submission. For example, when all fields of a nested property are empty, you don’t want to process this item. In Rails, this is known as reject_if
in the nested-attributes code.
You can do so now in Reform using :skip_if
.
class SongForm < Reform::Form property :title, skip_if: lambda { |v, *| v == "Bad song" }
Now, consider the following validation.
form.validate(title: "Bad song")
Given that very parameter hash, this ignores the incoming title
property as if it wasn’t present in the hash. The title is not updated on the form or model, later.
This works for both properties and nested forms.
To ignore blank nested forms you can use a macro we provide.
class SongForm < Reform::Form property :band, skip_if: :all_blank do property :name property :label end
This does exactly what you think it does! And of course, this works with collections as well.
Dirty Tracker!
In older versions, when syncing the form to the model, Reform used to call every setter for every property – regardless whether they’d actually changed or not. Now, Reform tracks what field has changed.
form.validate(title: "Violins") form.changed?(:title) #=> true
In order to only update those fields that have changed you need to include Sync::SkipUnchanged
into your form.
class SongForm < Reform::Form include Sync::SkipUnchanged property :title property :genre
In #sync
, the #title=
writer on the model is only called when the title has actually changed. This is extremely helpful for doing advanced form processing with file uploads, etc.
Performance Speed-Up
I could achieve a speed-up of about 85% with an extremely simple trick. Reform internally uses Representable for all kinds of data transformations. It used to create and configure arbitrary representer classes at run-time, which was costly. Those representer classes are now computed once on the form class level resulting in an incredible speed-up and probably less memory footprint.
Dynamic Syncing
The way #sync
pushes all attributes back to the model is very generic. Generic code is a good thing. Generic code gets even better when it’s easily extendible. And this is what the new #sync
API offers you.
form.sync( title: lambda { |v, opts| form.model.title = sanitize(value) } )
You can now add a lambda per property which is then called when syncing only if that property was changed. As you can see, the block is called in the caller’s context and allows you to access the form itself, the environment, the processed value and more.
This is a great way to dynamically process a property at run-time.
Dynamic Saving
While the dynamic syncing might smash many problems you sometimes need to run code after the model was saved, for instance, to include the model’s ID in your workflow.
No problem with Reform 1.2!
form.save( image: lambda { |v, opts| upload!(v, form.model.id) } )
Opening Reform’s API for those two steps makes the form object a perfect extension for a Trailblazer operation and allows separating form logic from persistence – one of the key concepts of Trailblazer.
The ActiveForm Drama
Before letting you run to try out all those new things, I quickly want to comment on the “ActiveForm drama” that got averted before it even took off.
Rails recently included active_form into their main repository. I got a bit offended by that since ActiveForm clearly started as a clone of Reform and then got “rebranded” or “re-implemented”. I explicitly had to remind Rails core to add an attribution to this project which copies my README almost identically, and also recycles my DSL, API and all concepts like nesting or defining form fields explicitly instead of guessing.
While I’m cool with that in general, I’m not entirely cool when Rails does that. Those who’ve been with me for the last decade might know why.
Anyway, the Rails core team acted exemplary, apologised for the lack of attributions, removed the debatable repository from the core account, and more.
I’m not sure what they’re gonna do but my blood temperature is back to semi-hot and I don’t mind ActiveForm anymore. At least, the concept of “forms” has finally arrived in Rails core!
Also, I am pretty impressed by the Rails community and how this “accident” was handled on both sides. ❤
Any thoughts on strong parameters and reform?
LikeLike
Dan: I mention that in the README and several times in my book. Reform supersedes strong_parameters which in itself is a leaky, half-baked solution. A form knows which parameters it needs and what to ignore.
LikeLike
We should all use Rails while considering it a timed bomb:
Use it, but don’t get tied to it too much.
LikeLike
I dont wanna worry with the fields, I prefer Linker gem’s (https://github.com/glaucocustodio/linker) approach.
LikeLike
Anderson: Cool, didn’t know about that gem. Reform is designed as an explicit mapper, that’s why you “have” to define fields. We want that to avoid magical behaviour.
PikachuEXE: Well said! 😀
LikeLike
Is there any chance you have an ultra contrived example that i could use to learn how to put all of this together?
I’ve asked for help with your gem on SO (http://stackoverflow.com/questions/27662044/form-objects-use-reform-gem-with-user-pet-example) but so far not really getting any responses.
While i know this gem is not specific to Rails, i think a lot of users who are fed up with forms in rails would benefit from even like a simple proof of concept app to model after.
LikeLike