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.
validate
and written to the model using sync
or save
. → APIFor a technical architecture overview, read the Architecture section.
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
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
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.
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.
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.
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.
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.
Add this your Gemfile.
gem "reform"
gem "dry-validation"
Please use [dry-validation](http://dry-rb.org/gems/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
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 property
s. This makes half-baked solutions like strong_parameter
or attr_accessible
obsolete.
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.
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.
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 of model.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.
Every Reform form object inherits from Disposable::Twin
, making every form a twin and giving each form the entire twin API such as.
:default
.:type
and :nilify
.If you’re looking for a specific feature, make sure to check the Disposable documentation
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
Form fields are declared using ::property
.
Validations leverage the respective validation engine’s API, which be either ActiveModel
or dry-validations.
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"
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
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"
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.
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.
Injecting Objects: safe args can be passed in constructor
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
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"
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.
After validate
, you can access validation errors via errors
.
form.errors #=> {title: ["must be filled"]}
The returned Errors
object exposes the following methods.
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"
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.
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.
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.
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.
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
.
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.
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.
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.
This document describes available options for Reform’s declarative API.
Every Reform form object inherits from Disposable::Twin
, making every form a twin and giving each form the entire twin API such as.
:default
.:type
and :nilify
.If you’re looking for a specific feature, make sure to check the Disposable documentation
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.
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"
Use writeable: false
to display a value but skip processing it in validate
.
property :country, writeable: false
model.country
to read the initial value.form.country=
in validate
.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"
Use readable: false
to hide a value but still write it to the model.
property :credit_card_number, readable: false
model.credit_card_number
and will display an empty field.validate
, the form calls form.credit_card_number=
.sync
, the setter model.credit_card_number=
is called and the value written to the database.Use parse: false
to protect the form setters from being called in validate
.
property :uuid, parse: false
model.uuid
to display the field via the form.validate
, the form’s setter won’t be called, leaving the value as it is.sync
, the setter model.uuid
is called and restored to the original value.Note that the :parse
option works by leveraging :deserializer.
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.
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.
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.
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.
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.
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.
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.
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.
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"
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.
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
Reform can also handle deeply nested hash fields from serialized hash columns. This is documented here.
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 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.
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.
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!
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.
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
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
.
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.
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.
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.
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.
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
.
[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"])
}
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"])
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.
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.
validate
, the form can’t find a corresponding nested songs form and calls the populate_if_empty
code.Song
model and assign it to the parent form via form.songs << Song.new
.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.
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.
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.
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.
: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 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.
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.
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
There’s different alternatives for setting a default value for a formerly empty field.
:prepopulator
as described here. Don’t forget to call prepopulate!
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 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
.
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.
In forms you’re upgrading to dry-validation, you can include the validation module explicitly.
require 'reform/form/dry'
<pre class=""><code class="rounded">class AlbumForm < Reform::Form feature Reform::Form::Dry
property :name
validation do params do required(:name).filled end end end </code></pre>
This replaces the ActiveModel backend with dry for this specific form class, only.
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 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.
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"
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
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.
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!
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.
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']}
Reform works with any framework, but comes with additional Rails glue code.
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 }}
.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.
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
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
.
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
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.
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
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
Reform provides the following ActiveRecord
specific features. They’re mixed in automatically in a Rails/AR setup.
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
Reform provides the following Mongoid
specific features. They’re mixed in automatically in a Rails/Mongoid setup.
validates_uniqueness_of
in your form.You may want to include the module manually then.
class SongForm < Reform::Form
include Reform::Form::Mongoid
ActiveRecord
or Mongoid
and form builder: require reform/form
, only.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
.Mongoid
constant is defined.We try to make upgrading as smooth as possible. Here’s the generic documentation, but don’t hesitate to ask for help on Zulip.
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.
In a Rails environment with ActiveModel/ActiveRecord, you have to include the reform-rails gem.
gem "reform"
gem "reform-rails"
Validations like validates_acceptance_of
are not available anymore, you have to use the new syntax.
validates acceptance: true
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.
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!
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