Forget about Nested Permission Attributes in Controllers with Form Objects
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.
Let’s start
If you have any questions, email us info@sumatosoft.com