Reform
- Last updated 14 Dec 22
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 usingsync
orsave
. â 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
feature Reform::Form::Dry
property :name
validation do
params do
required(:name).filled
end
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
feature Reform::Form::Dry
property :name
validation do
params { required(:name).filled }
end
property :artist do
property :name
validation do
params { 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 thereform-rails
gem. You have to add it to yourGemfile
.- 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 definedproperty
s. This makes half-baked solutions likestrong_parameter
orattr_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.
#initialize
always requires a model that the form represents.#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.#errors
returns validation messages in a classic ActiveModel style.#sync
writes form data back to the model. This will only use setter methods on the model(s). It returns the underlying model.#save
(optional) will call#save
on the model and nested models. Note that this implies a#sync
call. It returns the result ofmodel.save
.#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
feature Reform::Form::Dry
property :name
validation do
params do
required(:name).filled
end
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
feature Reform::Form::Dry
property :name
validation do
params { required(:name).filled }
end
property :artist do
property :name
validation do
params { 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 validate
ing. 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
- The form will invoke
model.country
to read the initial value. - It will invoke
form.country=
invalidate
. - The modelâs setter
model.country
wonât be called insync
.
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
- The form wonât invoke
model.credit_card_number
and will display an empty field. - In
validate
, the form callsform.credit_card_number=
. - In
sync
, the settermodel.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
- This will call
model.uuid
to display the field via the form. - In
validate
, the formâs setter wonât be called, leaving the value as it is. - In
sync
, the settermodel.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.
- In
validate
, the form canât find a corresponding nested songs form and calls thepopulate_if_empty
code. - The populator will create a
Song
model and assign it to the parent form viaform.songs << Song.new
. - This crashes, as
form.songs
isnil
.
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.
- Use
:prepopulator
as described here. Donât forget to callprepopulate!
before rendering the form. -
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.
config.reform.validations = :active_model
The configuration assumes you have reform-rails
installed.
Reform::Form.send(:include, Reform::Form::ActiveModel::Validations)
In a Ruby environment, youâd usually monkey-patch the Form
class.
In forms youâre upgrading to dry-validation, you can include the validation module explicitly.
require 'reform/form/dry'
class AlbumForm < Reform::Form
feature Reform::Form::Dry
property :name
validation do
params do
required(:name).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 name: :default do
params { required(:name).filled }
end
validation name: :artist, if: :default do
params { required(:artist).filled }
end
validation name: :famous, after: :default do
params { optional(:artist) }
rule(:artist) do
if value
key.failure('only famous artist') unless value =~ /famous/
end
end
end
This will only check for the artist
presence as well only 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
.
At any time you can extend an existing group using :inherit
(this feature is not compatible with dry-validation 1.x, to avoid any hacky solution we are waiting dry-v authors to implement it from their end first).
validation :email, inherit: true do
params { 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.
class AlbumForm < Reform::Form
feature Reform::Form::Dry
property :name
validation name: :default do
option :form
params do
required(:name).filled
end
rule(:name) do
key.failure('must be unique') if Album.where.not(id: form.model.id).where(name: value).exists?
end
end
end
The validation block is what dry-v calls contract which can contains params
, rule
and config
.
params
is a dry-v Schema
and will contain all the basic built in predicates, instead in the rule
block is where is possible to implement custom predicates.
Remember that the rule
block will not be executed in case the relative schema does not pass the validations. The form
object is always passed into the validation
block and it can be exposed using option :form
.
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
config.messages.load_paths << 'config/error_messages.yml'
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 withform_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 Rails form gems like simple_form
or Rails form_for
donât forget to include the rails-reform
gem in your Gemfile.
gem "reform-rails"
When using ActiveModel
validations, this is all you have to do.
However, if youâve configured dry-validation as your validation framework you have to include at least the FormBuilderMethods
module. This is necessary to translate Railsâ suboptimal songs_attributes weirdness back to normal songs:
naming in #validate
.
You can configure reform-rails
do enable form builder support with Dry-backed forms.
# config/development.rb
Rails.application.configure do
config.reform.enable_active_model_builder_methods = true
end
The manual way would be as follows.
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
- In case you explicitly donât want to have automatic support for
ActiveRecord
orMongoid
and form builder:require reform/form
, only. - In some setups around Rails 4 the
Form::ActiveRecord
module is not loaded properly, usually triggering aNoMethodError
sayingundefined method 'model'
. If that happened to you,require 'reform/rails'
manually at the bottom of yourconfig/application.rb
. - 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 Zulip.
2.2 to 2.3
If you have been using dry-validation and you want to upgrade to version 1.x, get ready to change a lot of your code, unfortunately dry-validation API has been completely rewritten so we had to adapt. If instead you have ActiveModel/ActiveRecord the upgrade from 2.2 to 2.3 should be nice and easy. Please refer to the CHANGES in the repo.
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