Decouple your models from forms. Reform gives you a form object with validations and nested setup of models. It is completely framework-agnostic and doesn't care about your database.
Add this line to your Gemfile:
gem 'reform'Say you need a form for song requests on a radio station. Internally, this would imply associating songs table and the artists table. You don't wanna reflect that in your web form, do you?
class SongRequestForm < Reform::Form
include DSL
property :title, on: :song
property :name, on: :artist
validates :name, :title, presence: true
endThe ::property method allows defining the fields of the form. Using :on delegates this field to a nested object in your form.
Note: There is a convenience method ::properties that allows you to pass an array of fields at one time.
In your controller you'd create a form instance and pass in the models you wanna work on.
def new
@form = SongRequestForm.new(song: Song.new, artist: Artist.new)
endYou can also setup the form for editing existing items.
def edit
@form = SongRequestForm.new(song: Song.find(1), artist: Artist.find(2))
endYour @form is now ready to be rendered, either do it yourself or use something like simple_form.
= simple_form_for @form do |f|
= f.input :name
= f.input :titleAfter a form submission, you wanna validate the input.
def create
@form = SongRequestForm.new(song: Song.new, artist: Artist.new)
#=> params: {song_request: {title: "Rio", name: "Duran Duran"}}
if @form.validate(params[:song_request])Reform uses the validations you provided in the form - and nothing else.
We provide a bullet-proof way to save your form data: by letting you do it!
if @form.validate(params[:song_request])
@form.save do |data, nested|
#=> data: <title: "Rio", name: "Duran Duran">
#
# nested: {song: {title: "Rio"},
# artist: {name: "Duran Duran"}}
SongRequest.new(nested[:song][:title])
endWhile data gives you an object exposing the form property readers, nested already reflects the nesting you defined in your form earlier.
To push the incoming data to the models directly, call #save without the block.
@form.save #=> populates song and artist with incoming data
# by calling @form.song.name= and @form.artist.title=.Often you want incoming form data to be converted to a type, like timestamps. Reform uses virtus for coercion, the DSL is seamlessly integrated into Reform with the :type option.
Be sure to add virtus to your Gemfile.
require 'reform/form/coercion'
class SongRequestForm < Reform::Form
include DSL
include Reform::Form::Coercion
property :written_at, on: :song, type: DateTime
end
@form.save do |data, nested|
data.written_at #=> <DateTime XXX>A sample Rails app using Reform.
Reform offers ActiveRecord support to easily make this accessible in Rails based projects. You simply include Reform::Form::ActiveRecord in your form object and the Rails specific code will be handled for you. This happens by adding behaviour to make the form ActiveModel-compliant. Note that this module will also work with other ORMs like Datamapper.
You have to include a call to model to specify which is the main object of the form.
require 'reform/rails'
class UserProfileForm < Reform::Form
include DSL
include Reform::Form::ActiveRecord
property :email, on: :user
properties [:gender, :age], on: :profile
model :user
validates :email, :gender, presence: true
validates :age, numericality: true
validates_uniqueness_of :email
endBasically, model :user tells Reform to use the :user object in the composition as the form main object while using "user" as the form name (needed for URL computation). If you want to change the form name let Reform know.
model :singer, :on => :user # form name is "singer" whereas main object is `:user` in composition.The form becomes very dumb as it knows nothing about the backend assocations or data binding to the database layer. This simply takes input and passes it along to the controller as it should.
<%= form_for @form do |f| %>
<%= f.email_field :email %>
<%= f.input :gender %>
<%= f.number_field :age %>
<%= f.submit %>
<% end %>In the controller you can easily create helpers to build these form objects for you. In the create and update actions Reform allows you total control of what to do with the data being passed via the form. How you interact with the data is entirely up to you.
class UsersController < ApplicationController
def create
@form = create_new_form
if @form.validate(params[:user])
@form.save do |data, map|
new_user = User.new(map[:user])
new_user.build_user_profile(map[:profile])
new_user.save!
end
end
end
private
def create_new_form
UserProfileForm.new(user: User.new, profile: UserProfile.new)
end
endNote: this can also be used for the update action as well.
Sometimes you want to access your database in a validation. You can access the models using the #model accessor in the form.
class ArtistForm < Reform::Form
property :name
validate "name_correct?"
def name_correct?
errors.add :name, "#{name} is stupid!" if model.artist.stupid_name?(name)
end
endBy explicitely defining the form layout using ::property there is no more need for protecting from unwanted input. strong_parameter or attr_accessible become obsolete. Reform will simply ignore undefined incoming parameters.
Great thanks to Blake Education for giving us the freedom and time to develop this project while working on their project.