In previous tutorial we discussed refactoring code and decorators pattern. In this tutorial we will take our discussion further to form objects pattern. We use forms in our application to take inputs from user. The form data is received in our controller, where we have some logic to check data and save inputs to database. While refactoring our code we should keep single responsibility principle (SRP) in mind. SRP states that a class should have single responsibility of a functionality.
A form object is nothing but the replacement of model object for form. The form object should respond to methods like active record objects.
Model objects generally have lots of functionality. All validations are placed in model that may be needed in certain cases only and hardly needed to be in model. We can extract these validations out from model using form objects. Please note, validations are important and should be in model to check the data integrity. We can not just extract all the validations out of model.
Form objects is also useful in the cases where we create multiple model objects in same form. Its better to use form objects than accepts_nested_attributes_for.
The form class will have all the attributes of the form fields. All we need is create a class that will encapsulate the functionality of the form. We can include ActiveModel::Model (Rails 4) in our class to behave like active record model, which will let us use active record validation helpers and form helpers for our form.
Let's see with an example. We have a user registration controller where we register a new user.
# app/controllers/registration_controller.rb
class RegistrationsController < ApplicationController
respond_to :html
def new
@user = User.new
end
def create
@user = User.new(user_params)
if @user.save
redirect_to @user
else
render :new
end
end
private
def user_params
params.require(:user).permit(:email, :password, :name)
end
end
And the registration form #
# app/views/registration/new.html.erb
<%= form_for @user do |f| %>
<% if @user.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(@user.errors.count, "error") %> prohibited this msg from being saved:</h2>
<ul>
<% @user.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<%= f.label :email, 'Email' %>:
<%= f.text_field :email %>
<%= f.label :password, 'Password' %>:
<%= f.password_field :password %>
<%= f.label :name, 'Name' %>:
<%= f.text_field :name %>
<%= f.submit %>
<% end %>
Now we transform the above code to form object pattern by creating a simple class that encapsulate the logic to register user. I will put my forms files inside app/forms/ :
# app/forms/registration.rb
class Registration
include ActiveModel::Model
attr_reader(:email, :password, :name)
def initialize(options={})
@name = options[:name]
@email = options[:email]
@password = options[:password]
end
validates :email, presence: true
validates :name, presence: true
validates :password, presence: true
def save
if valid?
create_user
end
end
def persisted?
false
end
private
def create_user
User.create({email: email, password: password, name: name})
end
end
Change registration controller:
# app/controllers/registration_controller.rb
class RegistrationsController < ApplicationController
respond_to :html
def new
@registration = Registration.new
end
def create
@registration = Registration.new(registration_params)
if @user = @registration.save
redirect_to @user
else
render :new
end
end
private
def registration_params
params.require(:registration).permit(:email, :password, :name)
end
end
And change our registration form #
# app/views/registration/new.html.erb
<%= form_for @registration do |f| %>
<% if @registration.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(@registration.errors.count, "error") %> prohibited this msg from being saved:</h2>
<ul>
<% @registration.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<%= f.label :email, 'Email' %>:
<%= f.text_field :email %>
<%= f.label :password, 'Password' %>:
<%= f.password_field :password %>
<%= f.label :first_name, 'First Name' %>:
<%= f.text_field :first_name %>
<%= f.submit %>
<% end %>
We can also include Virtus in our form object class. It helps to add object attributes and data type. It saves a lot of coding that may require in the form objects class. There is no need to define initialize function in our class with Virtus.
Example:
class RegistrationForm
include ActiveModel::Model
include Virtus.model
attribute :name, String
attribute :email, String
attribute :password, String
validates :email, presence: true
validates :name, presence: true
validates :password, presence: true
def save
if valid?
create_user
end
end
def persisted?
false
end
private
def create_user
User.create({email: email, password: password, name: name})
end
end
Note:
In Rails 4
include ActiveModel::Model
In Rails 3
extend ActiveModel::Naming
include ActiveModel::Conversion
include ActiveModel::Validations
0 Comment(s)