If you are tired of big nested permission attributes in controllers and messed up relations and accepts_nested_attributes_for in models, then you will find much useful information in this article. Go on reading!
The best way to eliminate the redundancies mentioned above is to add another abstraction layer – form objects. Using this way, we will be able to leave associations, scopes, and finders in models only, and probably some persistent logic. What we’re going to do is to remove validations, business logic, and all the rest. In controllers’ actions endpoints will remain which call form objects and pass parameters into them.
Which facilities you’d better use:
At first, let’s have a look at Reform.
The creation of a FormObject for User entity is a pretty simple task:
class UserForm < Reform::Form property :name validates :name, presence: true end
Then there is what we see concerning the controller:
class UsersController def new @form = UserForm.new(User.new) end def create @form = UserForm.new(User.new) if @form.validate(params[:user]) @form.save ... else ... end end def edit user = User.find(params[:id]) @form = UserForm.new(user) end
It’s also pretty easy to render a form into view:
= form_for @form do |f| = f.input :name
The FormObject lets map data to be separated from models and validations:
class UserForm < Reform::Form include Reform::Form::ActiveRecord include Composition model :company property :name, on: :user property :email, on: :user property :company_name, on: :company, from: :name validates :name, presence: truevalidates :email, presence: true validates :company_name, presence: true def save super model[:user].update(company: model[:company]) end end
It will have the following view:
= simple_form_for @form do |f| - f.input :name - f.input :email - f.input :company_name, label: 'Company Name'
Let’s consider a more complex example and see how the FormObject can help us in removing all these accepts_nested_attributes_for from the models we’ve built and business logic:
class User < ActiveRecord::Base has_many :permissions, class_name: 'User::Permission' has_many :pay_rates, class_name: 'User::PayRate' has_many :addresses, class_name: 'User::Address', dependent: :destroy has_many :licenses, class_name: 'User::License' ... ... accepts_nested_attributes_for :permissions, reject_if: :all_blank, allow_destroy: true accepts_nested_attributes_for :pay_rates, reject_if: :all_blank, allow_destroy: true accepts_nested_attributes_for :addresses, reject_if: :all_blank, allow_destroy: true accepts_nested_attributes_for :licenses, reject_if: :all_blank, allow_destroy: true ... ... def build_dependencies self.permissions ||= User::Permission.new permissions.build_dependencies self.pay_rates ||= User::PayRate.new pay_rates.build_dependencies self.licenses ||= User::License.new licenses.build_dependencies self.addresses ||= User::Address.new addresses.build_dependencies ... ... end end
Now, it is time to see what we have in the controller:
Looks not really good, right?
Look what happens if we move validations and nesting objects to the FormObject:
class User::Permission < ActiveRecord::Base belongs_to :user ... ... end class User::PermissionFormObject < Reform::Form property :human_resources property :payrol property :clinical_assignments property :patient_schedule property :referral property :md_order property :visit_note property :chha_assignmen property :chha_supervisory_visit_note property :lvn_supervisory_visit_not property :case_conference property :progress_report property :plan_of_car property :medication_profile property :incident_repor property :infection_report property :discharge_summar property :discharge_instruction property :notice_of_non_coverag property :hhabn property :visit_note_co_sign property :md_orders_co_sign validate_presence_of :human_resources, :payroll, :clinical_assignments, :patient_schedule, :referral, :md_orders, :visit_note, :chha_assignment, :chha_supervisory_visit_note, :lvn_supervisory_visit_note, :case_conference, :progress_report, :plan_of_care, :medication_profile, :incident_report, :infection_report, :discharge_summary, :discharge_instruction, :notice_of_non_coverage, :hhabn, :visit_note_co_sign, :md_orders_co_sign # More complex validations for permissions ... ... ... end
Then, let us consider the UserForm:
class UserForm < Reform::Form # user properties collection :permissions, form: User::PermissionForm, populate_if_empty: User::Permission collection :pay_rates, form: User::PayRateForm, populate_if_empty: User::PayRate collection :licenses, form: User::LicenseForm, populate_if_empty: User::License collection :addresses, form: User::AddressForm, populate_if_empty: User::Address # user validations end
This is how we moved validations and mapping attributes from models to forms. Besides, we’ve simplified the process of forms rendering. Now, It’s not a big deal to implement such a task, do you agree? Follow our blog to learn other development techniques.