Reform 2.0 – Form Objects for Ruby.

A few days ago I pushed the next version of Reform: Version 2. While this is still a release candidate, it can be considered stable.

The reason I blog as if it was a major release is: I want you to test, try, and complain. Speak now or forever hold your peace! Now is the time to make me add or change features before we push the final stable 2.0

Here’s why Reform 2 was necessary, and, of course, why it’s awesome.

UPDATE: This is a release note directed to Reform users. If you want to learn more about Reform, read an introduction post.

Too Big!

There’s not a single amazing new feature in Reform 2. This is, if you quickly skim over the changes.

Of course, a lot of things have changed, but more on the inside of Reform.

Reform was getting too big. The form object was doing presentation, deserialization of incoming data, data mapping, coercion, validation, writing to persistence and saving.

For a gem author, monster objects are (or should be!) a nightmare. It is incredibly hard to follow what happens where in big objects, so I extracted a huge chunk of logic into a separate gem.

The form object now really only does validation, everything else is handled via Disposable and Representable.

The Architecture Now.

Both deserialization and mapping form data to persistence objects like ActiveRecord models is now completely decoupled.

architecture-reform-2

To cut it short: Deserializing of the params hash into a validatable object graph is done by a representer. Validation happens in the form itself. Coercion, syncing and saving all happens in the form’s twin.

Less Representable.

I removed a lot of representable-specific mapping logic, mainly because it was incredibly hard to understand. For example, you can now actually grasp what methods like #prepopulate! do by looking at the source.

This has also sped up Reform by 50%. That’s right – it is much faster now thanks to explicit, simple transformation logic.

No Rails, More Lotus!

Reform 1 used ActiveModel::Validations for validations. This still works, but you can also chuck Rails into the bin and use Lotus::Validations insteadremoving any Rails dependency from your forms.

class SongForm < Reform::Form
  include Reform::Lotus

  property :title
  validates :title, presence: true
end

While Reform was dragging the activemodel dependency around, this is now up to you. Reform still supports Rails but with a very low gravity.

Deserialization.

In #validate, to parse the incoming params into the object graph, an external representer is used. This could be any kind of representer and thus allows you to parse JSON, XML and other formats easily into an object graph.

Nevertheless, the representer will simply operate on the twin API to populate the form. This means, you can basically use your own deserialization logic.

form = SongForm.new(song)

form.title = "Madhouse"
form.band = Band.new
form.band.name = "Bombshell Rocks"

form.validate({})

The above example is a naive implementation of a deserializer without overriding parts of validate. You can set properties and add or removed nested objects. The twin will take care of mapping that to its object graph.

Forms and JSON

Trailblazer takes advantage of that already and allows JSON “contracts” that can deserialize and validate JSON documents.

You can do that manually, too.

class SongRepresenter < Roar::Decorator
  include JSON
  property :title
end

form.validate('{"title": "Melanie Banks"}') do |json|
  SongRepresenter.new(form).from_json(json)
end

This will use SongRepresenter for the deserialization. The representer will assign form.title=. After that, the form will proceed with its normal validation logic as if the form was a hash-based one.

In case I missed to make my point: This allows using forms for document APIs!

Coercion

In earlier versions, Reform implemented coercion in the deserialization representer which sometimes was kinda awkward. Coercion now happens in the twin.

form.created_at = "1/1/1998"
form.created_at #=> 

You can also override the form’s setter methods to build your own typecasting logic. Many people did that already in Reform 1, but in combination with the representer this could mess things up.

Populators

When deserializing, Reform per default tries to find the matching nested form for you. Often, there is no nested form, yet, that’s why we provide options like :populate_if_empty that will add a nested form corresponding to the particular input fragment.

Using the :populator option was a bit tedious and you needed quite some knowledge about how forms work. This has changed in Reform 2 and is super simple now.

In a populator, you can use the twin API to modify the object graph.

populator: lambda do |fragment, collection, index, options|
  collection << Song.new
end

This primitive populator will always add a new song object to the existing collection. Note how you do not have to care about adding a nested form anymore, as you used to have in Reform 1. The twin will do this for you.

Pre-populators

I’ve seen many users writing quirks to “fill out” a form before it is rendered, for example, to provide default values for visual fields or pre-selecting a radio button.

Reform 2 introduces the concept of prepopulators that can be configured per property.

property :title, prepopulator: lambda { self.title = "The title" }

Again, prepopulators can use the twin API to set up an arbitrary object graph state. They have to be run explicitly, usually before rendering, using #prepopulate!.

Hash Fields

A feature I personally love in Reform 2 is Struct. It allows to map hashes to properties.

Say you had a serialized hash column in your songs table.

class Song  {admin: {read: true, write: true}, user: {destroy: false}}

“Working with hashes is fun!” said no one ever. Instead, let Reform map that to objects.

class SongForm < Reform::Form
  property :settings, struct: true do
    property :admin, struct: true do
      property :read
      property :write
      validates :read, inclusion: [true, false]
    end
  end
end

You can have an unlimited number of nestings in the hash. Every nesting results in a nested form twin to work with.

The Struct feature is described in this blog post in greater detail.

Syncing and Saving

The sync and save method both completely got extracted and are now implemented in Disposable.

Option Methods

A nice addition that I use a lot is option methods: you can specify dynamic options not only with a lambda, but also as a symbol referencing an instance method.

property :composer, populate_if_empty: :populate_composer! do
  # ..
end
 
def populate_composer!(fragment, options)
  Artist.new
end

This greatly cleans up forms when they become more complex. A cool side-effect: you can use inheritance better, too, and reuse option methods.

State Tracking

Since nested forms are now implemented as twins, you can use Disposable’s state tracking to follow what was going on on your form in validate.

State tracking is incredibly helpful for Imperative Callbacks and other post-processing logic.

More Documentation!

As you might have noticed, I have started to document all my gems on the new Trailblazer page.

I’d like to point you to the upcoming Trailblazer book, too. In 11 chapters, it discusses every aspect of Reform you can think of, as Reform is an essential part of this new architecture.

As a side-note: I mainly wrote this book to save myself from answering particular questions a hundred times. The Trailblazer books really talks about all my gems in great detail, and it is a nice way to support a decade of Open-Source work for you, too.

Conclusion

With Reform 2.0, my dream architecture has become true, my vision of what a form object should do and what should be abstracted in a separate layer is implemented, and I am very happy with it.

The code should be significantly easier to read and change, too. And it is faster.

It all adds up – Reform 2 is already deployed on hundreds of production sites, so update today and let me know what you think!

Advertisements

4 thoughts on “Reform 2.0 – Form Objects for Ruby.

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 )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s