Join the social network of Tech Nerds, increase skill rank, get work, manage projects...
 
  • Form objects pattern (refactoring)

    • 0
    • 0
    • 0
    • 0
    • 0
    • 0
    • 0
    • 0
    • 397
    Comment on it

    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)

Sign In
                           OR                           
                           OR                           
Register

Sign up using

                           OR                           
Forgot Password
Fill out the form below and instructions to reset your password will be emailed to you:
Reset Password
Fill out the form below and reset your password: