Reform

  • Last updated 17 Jul 20

Reform provides form objects to run validations for one or multiple models.

Overview

Validations no longer sit in model classes, but in forms. Once the data is coerced and validated, it can be written to the model.

A model can be any kind of Ruby object. Reform is completely framework-agnostic and doesn’t care about your database.

A form doesn’t have to be a UI component, necessarily! It can be an intermediate validation before writing data to the persistence layer. While form objects may be used to render graphical web forms, Reform is used in many pure-API applications for deserialization and validation.

  • API In Reform, form classes define fields and validations for the input. This input can be validated using validate and written to the model using sync or save. → API
  • DATA TYPES Reform can map model attributes, compositions of objects, nested models, hash fields and more. → DATA TYPES
  • COERCION When validating, the form can coerce input to arbitrary values using the dry-types gem. → COERCION
  • POPULATOR Deserialization of the incoming data can be customized using populators. → POPULATOR
  • VALIDATION GROUPS Validations can be chained or run when certain criteria match, only. → VALIDATION GROUPS

For a technical architecture overview, read the Architecture section.

API

Forms are defined in classes. Often, these classes partially map to a model.

class AlbumForm < Reform::Form
  property :title

  validation do
    required(:title).filled
  end
end
class AlbumForm < Reform::Form
  property :title

  validates :title, presence: true
end

Form fields are specified using property and collection, validations for the fields using the respective validation engine’s API.

Forms can also be nested and map to more complex object graphs.

class AlbumForm < Reform::Form
  property :title

  validation do
    required(:title).filled
  end

  property :artist do
    property :name

    validation do
      required(:name).filled
    end
  end
end
class AlbumForm < Reform::Form
  property :title

  validates :title, presence: true

  property :artist do
    property :name

    validates :name, presence: true
  end
end

While Reform is perfectly suited to map nested models with associations, it also allows mapping via composition, to hash fields, and more. Check out the supported data types.

Setup

In your controller or operation you create a form instance and pass in the models you want to validate data against.

class AlbumsController < ApplicationController
  def new
    @form = AlbumForm.new(Album.new)
  end

This will also work as an editing form with an existing album.

def edit
  @form = AlbumForm.new(Album.find(1))
end

In setup, Reform will read values from the model.

model = Album.find(1)
model.title #=> "The Aristocrats"

@form = AlbumForm.new(model)
@form.title #=> "The Aristocrats"

Once read, the original model’s values will never be accessed.

Rendering

Your @form is now ready to be rendered, either do it yourself or use something like Rails’ #form_for, simple_form or formtastic.

= form_for @form do |f|
  = f.input :title

Nested forms and collections can be easily rendered with fields_for, etc. Note that you no longer pass the model to the form builder, but the Reform instance.

Optionally, you might want to use the #prepopulate! method to pre-populate fields and prepare the form for rendering.

Validation

A submitted form is processed via validate.

result = @form.validate(title: "Greatest Hits")

By passing the incoming hash to validate, the input is written to the form and validated.

This usually happens in a processing controller action.

def create
  @form = AlbumForm.new(Album.new)

  if @form.validate(params[:album])
    # persist data
    @form.save
  end
end

After validation, the form’s values reflect the validated data.

@form.validate(title: "Greatest Hits")
@form.title #=> "Greatest Hits"

Note that the model remains untouched - validation solely happens on the form object.

model.title #=> "The Aristocrats"

Reform never writes anything to the models, until you tell it to do so.

Persisting

The easiest way to persist validated data is to call #save on the form.

if form.validate(params[:song])
  form.save
end

This will write the data to the model(s) using sync and then call album.save.

You may save data manually using save with a block.

form.save do |nested_hash|
  Album.create(title: nested_hash["title"])
end

Or you can let Reform write the validated data to the model(s) without saving anything.

form.sync # the album is unsaved!

This will updated the model’s attributes using its setter methods, but not save anything.

Installation

Add this your Gemfile.

gem "reform"
gem "dry-validation"

Please use dry-validation, which is our recommended validation engine. Put the following snippet into an initializer.

require "reform/form/dry"

Reform::Form.class_eval do
  include Reform::Form::Dry
end

Add this to your Gemfile.

gem "reform"
gem "reform-rails"

To use ActiveModel for validations put this into an initializer.

require "reform/form/active_model/validations"

Reform::Form.class_eval do
  include Reform::Form::ActiveModel::Validations
end

Things you should know when using ActiveModel with Reform.

  • ActiveModel support is provided by the reform-rails gem. You have to add it to your Gemfile.
  • The above last step of including ActiveModel::Validations is done automatically in a Rails environment.
  • Reform works fine with Rails 3.1-4.2. However, inheritance of validations with ActiveModel::Validations is broken in Rails 3.2 and 4.0.

Design Concepts

  • FRAMEWORK-AGNOSTIC Reform is completely framework-agnostic and is used in many projects with Rails, Sinatra, Hanami and more.

    For Rails, the reform-rails gem provides convenient glue code to make Reform work with Rails’ form builders and ActiveModel::Validations.

  • ORMs Reform works with any ORM or PORO - it has zero knowledge about underlying databases per design. The only requirements are reader and writer methods on the model(s) for defined properties.

  • DATA MAPPING Reform helps mapping one or many models to a form object. Nevertheless, Reform is not a full-blown data mapper. It still is a form object. Simple data mapping like composition, delegation or hash fields come from the Disposable gem.

    Should you require more complex mapping, use something such as ROM and pass it to the form object.

  • SECURITY Reform simply ignores unsolicited input in validate. It does so by only accepting values for defined propertys. This makes half-baked solutions like strong_parameter or attr_accessible obsolete.

Architecture

When experiencing Reform for the first time, it might seem to do a lot, too much: It decorates a model, parses the incoming data into some object graph, validates the data somehow and supports writing this data back to the model.

Actually, Reform is very simple and consists of several smaller objects. Each object has a very specific scope one does exactly one thing, where the actual form object orchestrates between those.

SETUP : When instantiating the form object with a model, it will read its properties’ values from the model. Internally, this happens because a form is simply a Twin. Twins are light-weight decorator objects from the Disposable gem.

For nested properties or collections, nested form objects will be created and wrap the respective contained models.

DESERIALIZATION : In the validate method, the incoming hash or document is parsed. Each known field is assigned to the form object, each nested fragment will be mapped to a nested form. This process is known as deserialization.

The internal deserializer used for this is actually a representer from the Representable gem. It is inferred automatically by Reform, but theoretically, you could provide your own deserializer that goes through the document and then calls setters on the form.

POPULATOR Nested fragments in the document often need to be mapped to existing or new models. This is where populators in Reform help to find the respective model(s), wrap them in a nested form object, and create a virtual object graph of the parsed data.

Populators are code snippets you define in the form class, but they are called from the deserializing representer and help parsing the document into a graph of objects.

VIRTUAL OBJECT GRAPH : After deserialization, the form object graph represents the input. All data in validate has been written to the virtual graph, not to the model. Once this graph is setup, it can be validated.

The deserialization process is the pivotal part in Reform. Where simple validation engines only allow formal validations, Reform allows rich business validations such as “When user signed in, and it’s the first order, allow maximum 10 items in the shopping cart!”.

VALIDATION : For the actual validation, Reform uses existing solutions such as dry-validation or ActiveModel::Validations. It passes the data to the validation engine in the appropriate format - usually, this is a hash representing the virtual object graph and its data.

The validation is then completely up to the engine. Reform doesn’t know what is happening, it is only interested in the result and error messages. Both are exposed via the form object after validation has been finished.

The decoupled validation is why Reform provides multiple validation engines.

SYNC/SAVE

After the validate call, nothing has been written to the model(s), yet. This has to be explicitly invoked via sync or save. Now, Reform will use its basic twin functionality again and write the virtual data to the models using public setter methods. Again, Reform knows nothing about ORMs or model specifics.

API

This document discusses Reform’s declarative API to define form classes and the instance API that is used at run-time on the form object, e.g. to validate an incoming hash.

More specific documentation about options to be passed to the property and collection method are to be found in the options documentation.

Overview

Forms have a ridiculously simple API with only a handful of public methods.

  1. #initialize always requires a model that the form represents.
  2. #validate(params) updates the form’s fields with the input data (only the form, not the model) and then runs all validations. The return value is the boolean result of the validations.
  3. #errors returns validation messages in a classic ActiveModel style.
  4. #sync writes form data back to the model. This will only use setter methods on the model(s). It returns the underlying model.
  5. #save (optional) will call #save on the model and nested models. Note that this implies a #sync call. It returns the result of model.save.
  6. #prepopulate! (optional) will run pre-population hooks to “fill out” your form before rendering.

In addition to the main API, forms expose accessors to the defined properties. This is used for rendering or manual operations.

Disposable API

Every Reform form object inherits from Disposable::Twin, making every form a twin and giving each form the entire twin API such as.

  • Defaults using :default.
  • Coercion using :type and :nilify.
  • Nesting
  • Composition
  • Hash fields

If you’re looking for a specific feature, make sure to check the Disposable documentation

Form Class

Forms are defined in classes. Often, these classes partially map to one or many model(s).

class AlbumForm < Reform::Form
  property :title

  validation do
    required(:title).filled
  end
end
class AlbumForm < Reform::Form
  property :title

  validates :title, presence: true
end

Form fields are declared using ::property.

Validations leverage the respective validation engine’s API, which be either ActiveModel or dry-validations.

Property

Use property to map scalar fields of your model to the form.

class AlbumForm < Reform::Form
  property :title
end

This will create accessors on the form and read the initial value from the model in setup.

model = Album.new(title: "Greatest Hits")
form  = AlbumForm.new(model)

form.title #=> "Greatest Hits"

Overriding Accessors

You’re free to override the form’s accessors for presentation or coercion.

class AlbumForm < Reform::Form
  property :title

  def title
    super.capitalize
  end
end

As always, use super for the original method.

This can also be used to provide a default value.

def title
  super || "not available"
end

Collection

When mapping an array field of the model, use collection.

class AlbumForm < Reform::Form
  collection :song_titles
end

This will create accessors on the form and read the initial

model = Album.new(song_titles: ["The Reflex", "Wild Boys"])

form = AlbumForm.new(model)
form.song_titles[0] #=> "The Reflex"

Nesting

To create forms for nested objects, both property and collection accept a block for the nested form definition.

class AlbumForm < Reform::Form
  property :artist do
    property :name
  end

  collection :songs do
    property :title
  end
end

Nesting will simply create an anonymous, nested Reform::Form class for the nested property.

It’s often helpful with has_many or belongs_to associations.

artist = Artist.new(name: "Duran Duran")
songs  = [Song.new(title: "The Reflex"), Song.new(title: "Wild Boys")]
model  = Album.new(artist: artist, songs: songs)

The accessors will now be nested.

form   = AlbumForm.new(model)
form.artist.name #=> "Duran Duran"
form.songs[0].title #=> "The Reflex"

All API semantics explained here may be applied to both the top form and nested forms.

Nesting: Explicit Form

Sometimes you want to specify an explicit form constant rather than an inline form. Use the form: option here.

property :song, form: SongForm

The nested SongForm refers to a stand-alone form class you have to provide.

Setup

Injecting Objects: safe args can be passed in constructor

Validate

You can define validation for every form property and for nested forms.

class AlbumForm < Reform::Form
  property :title

  validation do
   required(:title).filled
  end

  property :artist do
    property :name

    validation do
     required(:name).filled
    end
  end
end
class AlbumForm < Reform::Form
  property :title

  validates :title, presence: true

  property :artist do
    property :name

    validates :name, presence: true
  end
end

Validations will be run in validate.

form.validate(
  {
    title: "Best Of",
    artist: {
      name: "Billy Joel"
    }
  }
) #=> true

The returned value is the boolean result of the validations.

Reform will read all values it knows from the incoming hash, and it will ignore any unknown key/value pairs. This makes strong_parameters redundant. Accepted values will be written to the form using the public setter, e.g. form.title = "Best Of".

After validate, the form’s values will be overwritten.

form.artist.name #=> "Billy Joel"

The model won’t be touched, its values are still the original ones.

model.artist.name #=> "Duran Duran"

Deserialization and Populator

Very often, you need to give Reform some information how to create or find nested objects when validateing. This directive is called populator and documented here.

Errors

After validate, you can access validation errors via errors.

form.errors #=> {title: ["must be filled"]}

The returned Errors object exposes the following methods.

Save

Calling #save with a block will provide a nested hash of the form’s properties and values. This does not call #save on the models and allows you to implement the saving yourself.

The block parameter is a nested hash of the form input.

@form.save do |hash|
  hash      #=> {title: "Greatest Hits"}
  Album.create(hash)
end

You can always access the form’s model. This is helpful when you were using populators to set up objects when validating.

@form.save do |hash|
  album = @form.model

  album.update_attributes(hash[:album])
end

Reform will wrap defined nested objects in their own forms. This happens automatically when instantiating the form.

album.songs #=> [<Song name:"Run To The Hills">]

form = AlbumForm.new(album)
form.songs[0] #=> <SongForm model: <Song name:"Run To The Hills">>
form.songs[0].name #=> "Run To The Hills"

Nested Saving

validate will assign values to the nested forms. sync and save work analogue to the non-nested form, just in a recursive way.

The block form of #save would give you the following data.

@form.save do |nested|
  nested #=> {title:  "Greatest Hits",
         #    artist: {name: "Duran Duran"},
         #    songs: [{title: "Hungry Like The Wolf"},
         #            {title: "Last Chance On The Stairways"}]
         #   }
  end

The manual saving with block is not encouraged. You should rather check the Disposable docs to find out how to implement your manual tweak with the official API.

Turning Off Autosave

You can assign Reform to not call save on a particular nested model (per default, it is called automatically on all nested models).

class AlbumForm < Reform::Form
  # ...

  collection :songs, save: false do
    # ..
  end

The :save options set to false won’t save models.

Inheritance

Forms can be derived from other forms and will inherit all properties and validations.

class AlbumForm < Reform::Form
  property :title

  collection :songs do
    property :title

    validates :title, presence: true
  end
end

Now, a simple inheritance can add fields.

class CompilationForm < AlbumForm
  property :composers do
    property :name
  end
end

This will add composers to the existing fields.

You can also partially override fields using :inherit.

class CompilationForm < AlbumForm
  property :songs, inherit: true do
    property :band_id
    validates :band_id, presence: true
  end
end

Using inherit: here will extend the existing songs form with the band_id field. Note that this simply uses representable’s inheritance mechanism.

Forms In Modules

To maximize reusability, you can also define forms in modules and include them in other modules or classes.

module SongsForm
  include Reform::Form::Module

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

This can now be included into a real form.

class AlbumForm < Reform::Form
  property :title

  include SongsForm
end

Note that you can also override properties using inheritance in Reform.

When using coercion, make sure the including form already contains the Coercion module.

If you want to provide accessors in the module, you have to define them in the InstanceMethods module.

module SongForm
  include Reform::Form::Module

  property :title

  module InstanceMethods
    def title=(v)
      super(v.trim)
    end
  end
end

This is important so Reform can add your accessors after defining the default ones.

Dirty Tracker

Every form tracks changes in #validate and allows to check if a particular property value has changed using #changed?.

form.title => "Button Up"

form.validate("title" => "Just Kiddin'")
form.changed?(:title) #=> true

When including Sync::SkipUnchanged, the form won’t assign unchanged values anymore in #sync.

Deserialization

When invoking validate, Reform will parse the incoming hash and transform it into a graph of nested form objects that represent the input. This is called deserialization.

The deserialization is an important (and outstanding) feature of Reform and happens by using an internal representer that is automatically created for you. You can either configure that representer using the :deserializer option or provide code for deserialization yourself, bypassing any representer logic.

The deserialize! method is called before the actual validation of the graph is run and can be used for deserialization logic.

  class AlbumForm < Reform::Form
    property :title

    def deserialize!(document)
      hash = YAML.parse(document)

      self.title  = hash[:title]
      self.artist = Artist.new if hash[:artist]
    end
  end

We encourage you to use Reform’s deserialization using a representer, though. The representer is highly configurable and optimized for its job of parsing different data structures into Ruby objects.

Population

To hook into the deserialization process, the easiest way is using the :populator option. It allows manually creating, changing or adding nested objects to the form to represent the input.

Inflection

Properties can have arbitrary options that might become helpful, e.g. when rendering the form.

property :title, type: String

Use options_for to access a property’s configuration.

form.options_for(:title) # => {:readable=>true, :coercion_type=>String}

Note that Reform renames some options (e.g. :type internally becomes :coercion_type). Those names are private API and might be changed without deprecation.

Options

This document describes available options for Reform’s declarative API.

Disposable API

Every Reform form object inherits from Disposable::Twin, making every form a twin and giving each form the entire twin API such as.

  • Defaults using :default.
  • Coercion using :type and :nilify.
  • Nesting
  • Composition
  • Hash fields

If you’re looking for a specific feature, make sure to check the Disposable documentation

Virtual Attributes

Virtual fields come in handy when there’s no direct mapping to a model attribute or when you plan on displaying but not processing a value.

Virtual

Often, fields like password_confirmation should neither be read from nor written back to the model. Reform comes with the :virtual option to handle that case.

class PasswordForm < Reform::Form
  property :password
  property :password_confirmation, virtual: true

Here, the model won’t be queried for a password_confirmation field when creating and rendering the form. When saving the form, the input value is not written to the decorated model. It is only readable in validations and when saving the form manually.

form.validate("password" => "123", "password_confirmation" => "321")

form.password_confirmation #=> "321"

The nested hash in the block-#save provides the same value.

form.save do |nested|
  nested[:password_confirmation] #=> "321"

Read-Only

Use writeable: false to display a value but skip processing it in validate.

property :country, writeable: false
  1. The form will invoke model.country to read the initial value.
  2. It will invoke form.country= in validate.
  3. The model’s setter model.country won’t be called in sync.

Non-writeable values are still readable in the nested hash and through the form itself.

form.save do |nested|
  nested[:country] #=> "Australia"

Write-Only

Use readable: false to hide a value but still write it to the model.

property :credit_card_number, readable: false
  1. The form won’t invoke model.credit_card_number and will display an empty field.
  2. In validate, the form calls form.credit_card_number=.
  3. In sync, the setter model.credit_card_number= is called and the value written to the database.

Access Protection

Use parse: false to protect the form setters from being called in validate.

property :uuid, parse: false
  1. This will call model.uuid to display the field via the form.
  2. In validate, the form’s setter won’t be called, leaving the value as it is.
  3. In sync, the setter model.uuid is called and restored to the original value.

Note that the :parse option works by leveraging :deserializer.

Coercion

Incoming form data often needs conversion to a specific type, like timestamps. Reform uses dry-types for coercion. The DSL is seamlessly integrated with the :type option.

Be sure to add dry-types to your Gemfile when requiring coercion.

gem "dry-types"

To use coercion, you need to include the Coercion module into your form class.

require "reform/form/coercion"

class SongForm < Reform::Form
  feature Coercion

  property :written_at, type: Types::Form::DateTime
end

form.validate("written_at" => "26 September")

Coercion only happens in #validate, not during construction.

form.written_at #=> <DateTime "2014 September 26 00:00">

Available coercion types are documented here.

Manual Coercion

To filter values manually, you can override the setter in the form.

class SongForm < Reform::Form
  property :title

  def title=(value)
    super sanitize(value) # value is raw form input.
  end
end

Again, setters are only called in validate, not during construction.

Deserializer

A form object is just a twin. In validate, a representer is used to deserialize the incoming hash and populate the form twin graph. This means, you can use any representer you like and process data like JSON or XML, too.

Populator

When deserializing the incoming input in validate, advanced logic might be necessary to find nested objects from the database, or populate the form with arbitrary nested objects.

The :populator and its short-hand :populate_if_empty options provide custom deserialization logic and are documented here.

Inheritance

Forms can be derived from other forms and will inherit all properties and validations.

class AlbumForm < Reform::Form
  property :title

  collection :songs do
    property :title

    validates :title, presence: true
  end
end

Now, a simple inheritance can add fields.

class CompilationForm < AlbumForm
  property :composers do
    property :name
  end
end

This will add composers to the existing fields.

You can also partially override fields using :inherit.

class CompilationForm < AlbumForm
  property :songs, inherit: true do
    property :band_id
    validates :band_id, presence: true
  end
end

Using inherit: here will extend the existing songs form with the band_id field. Note that this simply uses representable’s inheritance mechanism.

Skip_if

Use :skip_if to ignore properties in #validate.

property :hit, skip_if: lambda { |fragment, *| fragment["title"].blank? }

This works for both properties and entire nested forms. The property will simply be ignored when deserializing, as if it had never been in the incoming hash/document.

For nested properties you can use :skip_if: :all_blank as a macro to ignore a nested form if all values are blank.

Note that this still runs validations for the property.

Multiparameter Dates

Composed multi-parameter dates as created by the Rails date helper are processed automatically when multi_params: true is set for the date property and the MultiParameterAttributes feature is included. As soon as Reform detects an incoming release_date(i1) or the like it is gonna be converted into a date.

class AlbumForm < Reform::Form
  feature Reform::Form::ActiveModel::FormBuilderMethods
  feature Reform::Form::MultiParameterAttributes

  collection :songs do
    feature Reform::Form::ActiveModel::FormBuilderMethods
    property :title
    property :release_date, :multi_params => true
    validates :title, :presence => true
  end
end

Note that the date will be nil when one of the components (year/month/day) is missing.

Data Types

Composition

Reform allows to map multiple models to one form. The complete documentation is here, however, this is how it works.

class AlbumForm < Reform::Form
  include Composition

  property :id,    on: :album
  property :title, on: :album
  property :songs, on: :cd
  property :cd_id, on: :cd, from: :id

  validates :title, presence: true
end

Note that Reform now needs to know about the source of properties. You can configure that by using the on: option.

Composition: Setup

When initializing a composition, you have to pass a hash that contains the composees.

form = AlbumForm.new(album: album, cd: CD.find(1))

The form now hides the fact that it represents more than one model. Accessors for properties are defined directly on the form.

form.title #=> "Greatest Hits"

Composition: Save/Sync

On a composition form, sync will write data back to the composee models. save will additionally call save on all composee models.

When using `#save’ with a block, here’s what the block parameters look like.

form.save do |nested|
  nested #=>
    {
      album:  {
        id:    9,
        title: "Rio"
      },
      cd:     {
        songs: [],
        id: 1
      }
    }
end

The hash is now keyed by composee name with the private property names.

Composition: ActiveModel

With ActiveModel, the form needs to have a main object configured. This is where ActiveModel-methods like #persisted? or ‘#id’ are delegated to. Use ::model to define the main object.

class AlbumForm < Reform::Form
  include Composition

  property :id,    on: :album
  property :title, on: :album
  property :songs, on: :cd
  property :cd_id, on: :cd, from: :id

  model :album # only needed in ActiveModel context.

  validates :title, presence: true
end

Hash Fields

Reform can also handle deeply nested hash fields from serialized hash columns. This is documented here.

Populators

Reform has two completely separated modes for form setup. One when rendering the form and one when populating the form in validate.

Prepopulating is helpful when you want to fill out fields (aka. defaults) or add nested forms before rendering. Populating is invoked in validate and will add nested forms depending on the incoming hash.

This page discusses the latter.

Populators, matching by IDs, deleting items, and much more, is discussed in detail in the chapters Nested Forms and Mastering Forms of the Trailblazer book.

Populators: The Problem

Populators in Reform are only involved when validating the form.

In #validate, you pass a nested hash to the form. Reform per default will try to match nested hashes to nested forms. But often the incoming hash and the existing object graph are not matching 1-to-1. That’s where populators enter the stage.

Let’s say you have the following model.

album = Album.new(songs: [])

The album contains an empty songs collection.

Your form looks like this.

class AlbumForm < Reform::Form
  collection :songs do
    property :name
  end
end

Here’s how you’d typically validate an incoming hash.

form = AlbumForm.new(album)
form.validate({songs: [{name: "Midnight Rendezvous"}]})

Reform will now try to deserialize every nested songs item to a nested form. So, in pseudo-code, this happens in validate.

form.songs[0].validate({name: "Midnight Rendezvous"})

Intuitively, you will expect Reform to create an additional song with the name “Midnight Rendezvous”. However, this is not how it works and will crash, since songs[0] doesn’t exist. There is no nested form to represent that fragment, yet, since the original songs collection in the model was empty!

Reform per design makes no assumptions about how to create nested models. You have to tell it what to do in this out-of-sync case.

You need to configure a populator to engage Reform in the proper deserialization.

Declarative DSL

You have to declare a populator when the form has to deserialize nested input. This can happen via :populate_if_empty or the generic :populator option.

Both options accept either a proc, a method symbol, or a Callable instance.

The proc is the most popular version.

property :artist, populator: ->(options) { .. } # proc

However, note that you can also provide a proc constant (here ArtistPopulator).

ArtistPopulator = ->(options) { .. }

property :artist, populator: ArtistPopulator

You can also use a method defined on the same level as the populator property (here #artist!).

property :artist, populator: :artist!

def artist!(options)
end

Or, a Uber::Callable-marked object.

class ArtistPopulator
  def call(options)
  end
end

property :artist, populator: ArtistPopulator.new

This is especially helpful when the populator gets complex and could benefit from inheritance/mixins.

Populator Invocation

Regardless of the populator type, keep in mind that a populator is only called if an incoming fragment for that property is present.

form.validate({songs: [{name: "Midnight Rendezvous"}]}) # songs present.

Running with our example, the following validation will not trigger any populator.

form.validate({})          # empty.
form.validate({songs: []}) # not empty, but no items!

Populate_if_empty

To let Reform create a new model wrapped by a nested form for you use :populate_if_empty. That’s the easiest form of population.

class AlbumForm < Reform::Form
  collection :songs, populate_if_empty: Song do
    property :name
  end
end

When traversing the incoming songs: collection, fragments without a counterpart nested form will be created for you with a new Song object.

form.validate({songs: [{name: "Midnight Rendezvous"}]})

Reform now creates a Song instance and nests it in the form since it couldn’t find form.songs[0].

Note that the matching from fragment to form works by index, any additional matching heuristic has to be implemented manually.

Populate_if_empty: Custom

You can also create the object yourself and leverage data from the traversed fragment, for instance, to try to find a Song object by name, first, before creating a new one.

class AlbumForm < Reform::Form
  collection :songs,
    populate_if_empty: ->(fragment:, **) do
      Song.find_by(name: fragment["name"]) or Song.new
    end

The result from this block will be automatically added to the form graph.

You can also provide an instance method on the respective form.

class AlbumForm < Reform::Form
  collection :songs, populate_if_empty: :populate_songs! do
    property :name
  end

  def populate_songs!(fragment:, **)
    Song.find_by(name: fragment["name"]) or Song.new
  end

Populate_if_empty: Arguments

The only argument passed to :populate_if_empty block or method is an options hash. It contains currently traversed :fragment, the :index (collections, only) and several more options.

The result of the block will be automatically assigned to the form for you. Note that you can’t use the twin API in here, for example to reorder a collection. If you want more flexibility, use :populator.

Populator

While the :populate_if_empty option is only called when no matching form was found for the input, the :populator option is always invoked and gives you maximum flexibility for population. They’re exclusive, you can only use one of the two.

Again, note that populators won’t be invoked if there’s no incoming fragment(s) for the populator’s property.

Populator: Collections

A :populator for collections is executed for every collection fragment in the incoming hash.

form.validate({
  songs: [
    {name: "Midnight Rendezvous"},
    {name: "Information Error"}
  ]
})

The following :populator will be executed twice.

class AlbumForm < Reform::Form
  collection :songs,
    populator: -> (collection:, index:, **) do
      if item = collection[index]
        item
      else
        collection.insert(index, Song.new)
      end
    end

This populator checks if a nested form is already existing by using collection[index]. While the index keyword argument represents where we are in the incoming array traversal, collection is a convenience from Reform, and is identical to self.songs.

Note that you manually have to check whether or not a nested form is already available (by index or ID) and then need to add it using the form API writers.

BTW, the :populator option accepts blocks and instance method names.

Populator: Return Value

It is very important that each :populator invocation returns the form that represents the fragment, and not the model. Otherwise, deserialization will fail.

Here are some return values.

populator: -> (collection:, index:, **) do
  songs[index]              # works, unless nil
  collection[index]         # identical to above
  songs.insert(1, Song.new) # works, returns form
  songs.append(Song.new)    # works, returns form
  Song.new                  # crashes, that's no form
  Song.find(1)              # crashes, that's no form

Always make sure you return a form object, and not a model.

Populator: Avoiding Index

In many ORMs, the order of has_many associations doesn’t matter, and you don’t need to use the index for appending.

collection :songs,
  populator: -> (collection:, index:, **) do
    if item = collection[index]
      item
    else
      collection.append(Song.new)
    end
  end

Often, it is better to match by ID instead of indexes.

Populator: Single Property

Naturally, a :populator for a single property is only called once.

class AlbumForm < Reform::Form
  property :composer,
    populator: -> (model:, **) do
      model || self.composer= Artist.new
    end

A single populator works identical to a collection one, except for the model argument, which is equally to self.composer.

Populator: Match by ID

[This is described in chapter Authentication in the Trailblazer book.]

Per default, Reform matches incoming hash fragments and nested forms by their order. It doesn’t know anything about IDs, UUIDs or other persistence mechanics.

You can use :populator to write your own matching for IDs.

collection :songs,
  populator: ->(fragment:, **) {
    # find out if incoming song is already added.
    item = songs.find { |song| song.id == fragment["id"].to_i }

    item ? item : songs.append(Song.new)
  }

Note that a :populator requires you to add/replace/update/delete the model yourself. You have access to the form API here since the block is executed in form instance context.

Again, it is important to return the new form and not the model.

This naturally works for single properties, too.

property :artist,
  populator: ->(fragment:, **) {
    artist ? artist : self.artist = Artist.find_by(id: fragment["id"])
  }

Delete

Populators can not only create, but also destroy. Let’s say the following input is passed in.

form.validate({
  songs: [
    {"name"=>"Midnight Rendezvous", "id"=>2, "delete"=>"1"},
    {"name"=>"Information Error"}
  ]
})

You can implement your own deletion.

collection :songs,
  populator: ->(fragment:, **) {
    # find out if incoming song is already added.
    item = songs.find { |song| song.id.to_s == fragment["id"].to_s }

    if fragment["delete"] == "1"
      songs.delete(item)
      return skip!
    end

    item ? item : songs.append(Song.new)
  }

You can delete items from the graph using delete. To avoid this fragment being further deserialized, use return skip! to stop processing for this fragment.

Note that you can also use the twin’s Collection API for finding nested twins by any field.

populator: ->(fragment:, **) {
  item = songs.find_by(id: fragment["id"])

Skip

Since Reform 2.1, populators can skip processing of a fragment by returning skip!. This will ignore this fragment as if it wasn’t present in the incoming hash.

collection :songs,
  populator: ->(fragment:, **) do
    return skip! if fragment["id"]
    # ..
  end

To skip from a Uber::Callable-marked object, return Representable::Pipeline::Stop

class SongsPopulator
  def call(options)
    return Representable::Pipeline::Stop if fragment["id"]
    # ...
  end
end

collection :songs, populator: SongsPopulator.new

This won’t process items that have an "id" field in their corresponding fragment.

Uninitialized Collections

A problem with populators can be an uninitialized collection property.

class AlbumForm < Reform::Form
  collection :songs, populate_if_empty: Song do
    property :title
  end
end

album = Album.new
form  = AlbumForm.new(album)

album.songs #=> nil
form.songs  #=> nil

form.validate(songs: [{title: "Friday"}])
#=> NoMethodError: undefined method `original' for nil:NilClass

What happens is as follows.

  1. In validate, the form can’t find a corresponding nested songs form and calls the populate_if_empty code.
  2. The populator will create a Song model and assign it to the parent form via form.songs << Song.new.
  3. This crashes, as form.songs is nil.

The solution is to initialize your object correctly. This is per design. It is your job to do that as Reform/Disposable is likely to do it wrong.

album = Album.new(songs: [])
form  = AlbumForm.new(album)

With ORMs, the setup happens automatically, this only appears when using Struct or other POROs as models.

Prepopulating

Reform has two completely separated modes for form setup. One when rendering the form and one when populating the form in validate.

Prepopulating is helpful when you want to fill out fields (aka. defaults) or add nested forms before rendering.

Populating is invoked in validate and will add nested forms depending on the incoming hash.

This page explains prepopulation used to prepare the form for rendering.

Configuration

You can use the :prepopulator option on every property or collection.

class AlbumForm < Reform::Form
  property :artist, prepopulator: ->(options) { self.artist = Artist.new } do
    property :name
  end

The option value can be a lambda or an instance method name.

In the block/method, you have access to the form API and can invoke any kind of logic to prepopulate your form. Note you need to assign models for nested form using their writers.

Invocation

Prepopulation must be invoked manually.

form = AlbumForm.new(Album.new)
form.artist #=> nil

form.prepopulate!

form.artist #=> <nested ArtistForm @model=<Artist ..>>

This explicit call must happen before the form gets rendered. For instance, in Trailblazer, this happens in the controller action.

Prepopulate is not Populate

:populator and :populate_if_empty will be run automatically in validate. Do not call prepopulate! before validate if you use the populator options. This will usually result in “more” nested forms being added as you wanted (unless you know what you’re doing).

Prepopulators are a concept designed to prepare a form for rendering, whereas populators are meant to set up the form in validate when the input hash is deserialized.

This is explained in the Nested Forms chapter of the Trailblazer book. Please read it first if you have trouble understanding this, and then open an issue.

Options

Options may be passed. They will be available in the :prepopulator block.

class AlbumForm < Reform::Form
  property :title, prepopulator: ->(options) { self.title = options[:def_title] }
end

You can then pass arbitrary arguments to prepopulate!.

form.title #=> nil

form.prepopulate!(def_title: "Roxanne")

form.title #=> "Roxanne"

The arguments passed to the prepopulate! call will be passed straight to the block/method.

This call will be applied to the entire nested form graph recursively after the currently traversed form’s prepopulators were run.

Execution

The blocks are run in form instance context, meaning you have access to all possible data you might need. With a symbol, the same-named method will be called on the form instance, too.

Note that you have to assign the pre-populated values to the form by using setters. In turn, the form will automatically create nested forms for you.

This is especially cool when populating collections.

property :songs,
  prepopulator: ->(*) { self.songs << Song.new if songs.size < 3 } do

This will always add an empty song form to the nested songs collection until three songs are attached. You can use the Twin::Collection API when adding, changing or deleting items from a collection.

Note that when calling #prepopulate!, your :prepopulate code for all existing forms in the graph will be executed . It is up to you to add checks if you need that.

Overriding

You don’t have to use the :prepopulator option. Instead, you can simply override #prepopulate! itself.

class AlbumForm < Reform::Form
  def prepopulate!(options)
    self.title = "Roxanne"
    self.artist = Artist.new(name: "The Police")
  end

Defaults

There’s different alternatives for setting a default value for a formerly empty field.

  1. Use :prepopulator as described here. Don’t forget to call prepopulate! before rendering the form.
  2. Override the reader of the property. This is not recommended as you might screw things up. Remember that the property reader is called for presentation (in the form builder) and for validation in #validate.

    property :title
    
    def title
      super or "Unnamed"
    end
    

Validation

Validation in Reform happens in the validate method, and only there.

Reform will deserialize the fragments and their values to the form and its nested subforms, and once this is done, run validations.

It returns the result boolean, and provide potential errors via errors.

Validation Engine

Since Reform 2.0, you can pick your validation engine. This can either be ActiveModel::Validations or dry-validation. The validation examples included on this page are using dry-validation.

Reform 2.2 drops ActiveModel-support. You can still use it (and it will work!), but we won't maintain it actively, anymore. In other words, ActiveModel::Validations and Reform should be working until at least Reform 4.0.

Note that you are not limited to one validation engine. When switching from ActiveModel::Validation to dry-validation, you should set the first as the default validation engine.

The configuration assumes you have reform-rails installed.

config.reform.validations = :active_model

In a Ruby environment, you’d usually monkey-patch the Form class.

  Reform::Form.send(:include, Reform::Form::ActiveModel::Validations)

In forms you’re upgrading to dry-validation, you can include the validation module explicitly.

require 'reform/form/dry'

module Album::Contract
  class Create < Reform::Form
    feature Reform::Form::Dry # override the default.

    validation do
      required(:title).filled
    end
  end
end

This replaces the ActiveModel backend with dry for this specific form class, only.

Validation Groups

Grouping validations enables you to run them conditionally, or in a specific order. You can use :if to specify what group had to be successful for it to be validated.

validation :default do
  required(:title).filled
end

validation :unique, if: :default do
  configure do
    def unique?(value)
      # ..
    end
  end

  required(:title, &:unique?)
end

This will only run the database-consuming :unique validation group if the :default group was valid.

Chaining groups works via the :after option. This will run the group regardless of the former result. Note that it still can be combined with :if.

validation :email, after: :default do
  configure do
    def email?(value)
      # ..
    end
  end
  required(:email, &:email?)
end

At any time you can extend an existing group using :inherit.

validation :email, inherit: true do
  required(:email).filled
end

This appends validations to the existing :email group.

Dry-validation

Dry-validation is the preferred backend for defining and executing validations.

The purest form of defining validations with this backend is by using a validation group. A group provides the exact same API as a Dry::Validation::Schema. You can learn all the details on the gem’s website.

require "reform/form/dry"

class AlbumForm < Reform::Form
  feature Reform::Form::Dry

  property :title

  validation :default do
    required(:title).filled
  end
end

Custom predicates have to be defined in the validation group. If you need access to your form you must pass with: {form: true} to your validation block.

validation :default, with: {form: true} do
  configure do
    def unique?(value)
      Album.where.not(id: form.model.id).find_by(title: value).nil?
    end
  end

  required(:title).filled(:unique?)
end

In addition to dry-validation’s API, you have access to the form that contains the group via form.

validation :default, with: {form: true} do
  configure do
    def same_password?(value)
      value == form.password
    end
  end

  required(:confirm_password).filled(:same_password?)
end

Make sure to read the documentation for dry-validation, as it contains some very powerful concepts like high-level rules that give you much richer validation semantics as compared to AM:V.

Dry: Error Messages

You need to provide custom error messages via dry-validation mechanics.

validation :default do
  configure do
    config.messages_file = 'config/error_messages.yml'
  end
  # ..
end

This is automatically configured to use the I18n gem if it’s available, which is true in a Rails environment.

A simple error messages file might look as follows.

en:
  errors:
    same_password?: "passwords not equal"

ActiveModel

In Rails environments, the AM support will be automatically loaded.

In other frameworks, you need to include Reform::Form::ActiveModel::Validations either into a particular form class, or simply into Reform::Form and make it available for all subclasses.

require "reform/form/active_model/validations"

Reform::Form.class_eval do
  feature Reform::Form::ActiveModel::Validations
end

Uniqueness Validation

Both ActiveRecord and Mongoid modules will support “native” uniqueness support where the validation is basically delegated to the “real” model class. This happens when you use validates_uniqueness_of and will respect options like :scope, etc.

class SongForm < Reform::Form
  include Reform::Form::ActiveRecord
  model :song

  property :title
  validates_uniqueness_of :title, scope: [:album_id, :artist_id]
end

Be warned, though, that those validators write to the model instance. Even though this usually is not persisted, this will mess up your application state, as in case of an invalid validation your model will have unexpected values.

This is not Reform’s fault but a design flaw in ActiveRecord’s validators.

Unique Validation

You’re encouraged to use Reform’s non-writing unique: true validation, though.

require "reform/form/validation/unique_validator"

class SongForm < Reform::Form
  property :title
  validates :title, unique: true
end

This will only validate the uniqueness of title.

For uniqueness validation of multiple fields, use the :scope option.

validates :user_id, unique: { scope: [:user_id, :song_id] }

Feel free to help us here!

Confirm Validation

Likewise, the confirm: true validation from ActiveResource is considered dangerous and should not be used. It also writes to the model and probably changes application state.

Instead, use your own virtual fields.

class SignInForm < Reform::Form
  property :password, virtual: true
  property :password_confirmation, virtual: true

  validate :password_ok? do
    errors.add(:password, "Password mismatch") if password != password_confirmation
  end
end

This is discussed in the Authentication chapter of the Trailblazer book.

Validations For File Uploads

In case you’re processing uploaded files with your form using CarrierWave, Paperclip, Dragonfly or Paperdragon we recommend using the awesome file_validators gem for file type and size validations.

class SongForm < Reform::Form
  property :image

  validates :image, file_size: {less_than: 2.megabytes},
    file_content_type: {allow: ['image/jpeg', 'image/png', 'image/gif']}

Rails

Reform works with any framework, but comes with additional Rails glue code.

Reform-Rails

The reform gem itself doesn’t contain any Rails-specific code but will still work, e.g. for JSON APIs. For extensive Rails support, add the reform-rails gem.

gem "reform", ">= 2.2.0"
gem "reform-rails"

Per default, reform-rails will assume you want ActiveModel::Validations as the validation engine. This will include the following into Reform::Form.

  • Form::ActiveModel for form builder compliance so your form works with form_for and friends.
  • Reform::Form::ActiveModel::FormBuilderMethods to make Reform consume Rails form builder’s weird parameters, e.g. {song_attributes: { number: 1 }}.
  • Uniqueness validation for ActiveRecord.

However, you can also use the new, recommended dry-validation backend, and you should check that out!

To do so, add the gem to your Gemfile.

gem "reform", ">= 2.2.0"
gem "reform-rails"
gem "dry-validation"

And configure Reform in an initializer, e.g. config/initializer/reform.rb to load the new validation backend.

 Rails.application.config.reform.validations = :dry

Make sure you use the API when writing dry validations.

Uniqueness Validation

Both ActiveRecord and Mongoid modules will support “native” uniqueness support from the model class when you use validates_uniqueness_of. They will provide options like :scope, etc.

You’re encouraged to use Reform’s non-writing unique: true validation, though. Learn more

ActiveModel Compliance

Forms in Reform can easily be made ActiveModel-compliant.

Note that this step is not necessary in a Rails environment.

class SongForm < Reform::Form
  include Reform::Form::ActiveModel
end

If you’re not happy with the model_name result, configure it manually via ::model.

class CoverSongForm < Reform::Form
  include Reform::Form::ActiveModel

  model :song
end

::model will configure ActiveModel’s naming logic. With Composition, this configures the main model of the form and should be called once.

This is especially helpful when your framework tries to render cover_song_path although you want to go with song_path.

FormBuilder Support

To make your forms work with all the form gems like simple_form or Rails form_for you need to include another module.

Again, this step is implicit in Rails and you don’t need to do it manually. If you’ve configured dry-validation as your validation framework the inclusion will not happen. You have to include at least the FormBuilderMethods module. This is needed to translate Rails’ suboptimal songs_attributes weirdness back to normal songs: naming in +#valiate+. This can be controlled via config.reform.enable_active_model_builder_methods = true.

class SongForm < Reform::Form
  include Reform::Form::ActiveModel
  include Reform::Form::ActiveModel::FormBuilderMethods
end

Simple Form

If you want full support for simple_form do as follows.

class SongForm < Reform::Form
  include Reform::Form::ActiveModel::ModelReflections

Including this module will add #column_for_attribute and other methods need by form builders to automatically guess the type of a property.

Validation Shortform

Luckily, this can be shortened as follows.

class SongForm < Reform::Form
  property :title, validates: {presence: true}
  property :length, validates: {numericality: true}
end

Use properties to bulk-specify fields.

class SongForm < Reform::Form
  properties :title, :length, validates: {presence: true} # both required!
  validates :length, numericality: true
end

Validations From Models

Sometimes when you still keep validations in your models (which you shouldn’t) copying them to a form might not feel right. In that case, you can let Reform automatically copy them.

class SongForm < Reform::Form
  property :title

  extend ActiveModel::ModelValidations
  copy_validations_from Song
end

Note how copy_validations_from copies over the validations allowing you to stay DRY.

This also works with Composition.

class SongForm < Reform::Form
  include Composition
  # ...

  extend ActiveModel::ModelValidations
  copy_validations_from song: Song, band: Band
end

ActiveRecord Compatibility

Reform provides the following ActiveRecord specific features. They’re mixed in automatically in a Rails/AR setup.

  • Uniqueness validations. Use validates_uniqueness_of in your form.

As mentioned in the Rails Integration section some Rails 4 setups do not properly load.

You may want to include the module manually then.

class SongForm < Reform::Form
  include Reform::Form::ActiveRecord

Mongoid Compatibility

Reform provides the following Mongoid specific features. They’re mixed in automatically in a Rails/Mongoid setup.

  • Uniqueness validations. Use validates_uniqueness_of in your form.

You may want to include the module manually then.

class SongForm < Reform::Form
  include Reform::Form::Mongoid

Troubleshooting

  1. In case you explicitly don’t want to have automatic support for ActiveRecord or Mongoid and form builder: require reform/form, only.
  2. In some setups around Rails 4 the Form::ActiveRecord module is not loaded properly, usually triggering a NoMethodError saying undefined method 'model'. If that happened to you, require 'reform/rails' manually at the bottom of your config/application.rb.
  3. Mongoid form gets loaded with the gem if Mongoid constant is defined.

Upgrading Guide

We try to make upgrading as smooth as possible. Here’s the generic documentation, but don’t hesitate to ask for help on Gitter.

2.1 to 2.2

In a Rails environment with ActiveModel/ActiveRecord, you have to include the reform-rails gem.

gem "reform"
gem "reform-rails"

1.2 to 2.0

Validations

Validations like validates_acceptance_of are not available anymore, you have to use the new syntax.

validates acceptance: true

Form#valid?

Using form.valid? is a private concept and was never publicly documented. It is still available (private) but you are strongly recommended to use #validate instead.

Form#update!

Apparently, some people used form.update!({..}) to pre-fillout forms. #update! has never been publicly documented and got removed in Reform 2. However, you can achieve the same behavior using the following hack.

Reform::Form.class_eval do
  alias_method :update!, :deserialize
  public :update!

Validation Backend

This only is necessary when not using reform/rails, which is automatically loaded in a Rails environment.

In an initializer, e.g. config/initializers/reform.rb.

require "reform/form/active_model/validations"
Reform::Form.class_eval do
  include Reform::Form::ActiveModel::Validations
end