Reform 1.2

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

Advertisement

6 thoughts on “Reform 1.2

  1. 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! 😀

    Like

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

    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