Reform 1.0 – Form Objects For Ruby And Rails

{{{
Dear friends – “Reform 1.0”:https://github.com/apotonick/reform is out. It took a while, and a lot of work went into thinking about changes and if they make sense. Not much of the public API has changed, which is a good sign. Internally, Reform has become simpler as I learned what Reform actually is: a validation concept with additional logic for UI and workflows.

The public API is now limited to a handful of methods with well-defined semantics. Tons of “discrepancies” were fixed by simplifying internals.

We also introduce @Reform::Contract@ which is an exiting concept to decouple validations entirely from your models. Even if you’re not interested in the form part of Reform, make sure to “check out contracts”:#contracts.

A form class still looks the same.

class AlbumForm < Reform::Form
  property :title
  validates :title, length: {minimum: 9}

  collection :songs do
    property :title
    validates :title, presence: true
  end

  validates :songs, length: {minimum: 2}
end

You gotta love that intuitive DSL – it has been copied in “several other form gems”:https://github.com/GCorbel/activeform-rails already, so it must be good!

h3. Unlimited Nesting.

I’m not sure if I like the fact but Reform can now do as many nestings as your crazy models need. In earlier versions there were problems that models in the 3rd layer and more didn’t get validated. Not anymore. Go nuts.

h3. Validations Against Nested Models.

In older versions it was a bit of a pain to validate, say, the minimum amount of nested @Song@s. This is all simplified now in Reform and as always, the simpler the better. Validations like the following just work now.

collection :songs do
  property :title
  validates :title, presence: true
end

validates :songs, length: {minimum: 2}

The validation will fail if there’s less than two @Song@ objects in the collection.

h3. Automatic Population.

A big show-stopper for lots of new users was when validating a new form with nested models: When rendering the form, they set up the form correctly.

AlbumForm.new(Album.new(songs: [Song.new, Song.new]))

This renders two song forms into the album form. Submitting usually ended in a fiasco of exceptions, as in the intercepting validating action, the code wasn’t setting up the object graph, again.

AlbumForm.new(Album.new).validate(params[:album])

Reform now tried to validate the incoming song data against Song models that weren’t there (@Album.new@ doesn’t provide Songs). This was a misunderstanding: Reform is not supposed to be stateful over requests and remember how many songs it displayed in the last request.

Whatever – you can tell Reform to “auto-populate” in @#validate@ now.

collection :songs, populate_if_empty: Song do
    property :title
    validates :title, presence: true
  end

This will create @Song@ instances where they’re missing in validate. You can “use a lambda”:https://github.com/apotonick/reform#populating-forms-for-validation and more options in case you wanna customize this process.

Lambdas are executed in the form’s context and need to return an instance (not the class).

collection :songs, 
  populate_if_empty: lambda { |input, args| 
    model.songs.build 
  },

This is all for @#validate@. I’ planning something similar for the rendering part to configure the number of forms to render, etc.

h3. Syncing.

Synchronizing data with the underlying model has caused some confusion, too. That’s why we split it into two parts with very limited behaviour scopes. BTW – many changes in Reform 1.0 were “triggered by vivid, colourful discussions”:https://github.com/apotonick/reform/issues/43 on the issues forum – I hope you guys keep contributing great ideas and criticism.

To write data back to your models, you use the @#sync@ method now. This will go through all models and use the specified writers to _sync_ data from the form to the models.

form = AlbumForm.new(Album.new)

form.validate(params[:album])

form.sync 
#=> album.title = "Best Of"
#=> album.songs[0].title = "Roxanne"
# and so on

Note that this does change the state of your (persistent) models – it does not save changes, yet!
}}}
{{{
h3. Saving.

When hitting the @#save@ method Reform will call @save@ on all models – unless you tell it not to do so:

collection :songs, save: false do
  property :title

In earlier version of Reform, saving would only call @save@ on the top model. The idea behind that was that the underlying models are saved using ActiveRecord’s @autosave: true@ feature. This design is still valid, however, Reform can do this for you, if you want it.

h3. Contracts.

This is by far my favourite refactoring: parts of @Form@ have been extracted into @Contract@ which allows validating models without the UI aspect. Allowing you to define nested validations in a separate layer paves the way for dumb data models that just contain associations and persistence-related logic “as targeted in Trailblazer”:https://github.com/apotonick/trailblazer.

A contract looks like a form. Actually, contracts can be derived from forms (and cells, and representers) automatically, but this would go too far now. Just keep in mind that there won’t be redundancy.

class AlbumContract < Reform::Contract
  property :title
  validates :title, length: {minimum: 9}

  collection :songs do
    property :title
    validates :title, presence: true
  end

This looks familiar. Now, a contract exposes three public methods.

album    = Album.find(1).update_attributes(..)
contract = AlbumContract.new(album)

The contract’s constructor accepts a model, just like a form.

if contract.validate
  album.save
else
  raise contract.errors.messages
end

You then use @validate@ to run validations on the underlying model. Note that it doesn’t accept params – remember, it’s a contract validating the state of a model.

Eventually, you wanna display errors by calling @errors@ on the contract.

The state of the model does not change during contract’s workflows.

See how contracts help you to decouple validations from your persistence layer? On long term, they will help you getting to a layered architecture.

An in-depth discussion of this architecture can be found in my upcoming book (scroll up, left!).

h3. Renaming

Finally, renaming works for all properties, whether it’s Composition or a model form or nested or whatever.

collection :songs, as: :tracks do
  property :title

This will expose songs as “tracks”, i.e. setters/getters on the form and in the HTM, it’ll say “tracks”.

h3. Internals.

Some things have changed in Reform 1.0. The internal workflows have been generalized. They all use “representable”:https://github.com/apotonick/representable for mapping data, it might look cryptic but once you got the hang of representable you will easily understand all the transformations that happen (I also added comments, some people complained about the lack of internal documentation).

The @Form@ class is nothing more than an entry point delegating to the requested behaviour. This is reflected in four new modules.

* @Setup@ contains transformation logic to populate the form when instantiating it.
* @Validate@ – surprisingly – implements the @#validate@ method along with the new @populator@ option.
* @Sync@ writes form data to models.
* @Save@ delegates @#save@ calls to all nested models.

This new file _and_ class layout makes it very easy to navigate through Reform’s codebase – personally, I started structuring all my other gems like that.

Every workflow is implemented by exposing exactly *one* public method (e.g. @#save@) which goes through the form’s attributes on that level only. It then calls itself _recursively_ on nested forms, making it a very clean implementation.
}}}

Advertisement

4 thoughts on “Reform 1.0 – Form Objects For Ruby And Rails

  1. Even though I have set a property as empty it is still calling the method on the underlying object :

    undefined method `months_ahead’ for #
    extracted source (around line #59): @form = NewJobForm.new @job

    collection :contracts do
    property :months_ahead, empty: true

    end

    What am I missing?

    Like

  2. It would be good if there was a quick way to make all properties empty. This would allow one to quickly create forms even though the tables hadn’t been created yet – a great advantage of reform.

    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