• Home
    >
  • Blog
    >
  • Forget about Nested Permission Attributes in Controllers with Form Objects

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:

<pre><code>class UserForm < Reform::Form</br>
property :name</br>
validates :name, presence: true</br>
end </code></pre>

Then there is what we see concerning the controller:

<pre><code>class UsersController</br>
def new</br>
  @form = UserForm.new(User.new)</br>
end</br>

def create</br>
  @form = UserForm.new(User.new)</br>
  if @form.validate(params[:user])</br>
    @form.save</br>
    ...</br>
  else</br>
    ...</br>
  end</br>
end</br>

def edit</br>
  user = User.find(params[:id])</br>
  @form = UserForm.new(user)</br>
end</br>

def update</br>
  user = User.find(params[:id])</br>
  @form = UserForm.new(user)</br>
  if @form.validate(params[:user])</br>
    @form.save</br>
    ...</br>
  else</br>
    ...</br>
  end</br>
end</br>
end </code></pre>

It’s also pretty easy to render a form into view:

<pre></code>= form_for @form do |f|</br>
= f.input :name</code></pre>

The FormObject lets map data to be separated from models and validations:

<pre><code>class UserForm < Reform::Form</br>
include Reform::Form::ActiveRecord</br>
include Composition</br>

model :company</br>

property :name, on: :user</br>
property :email, on: :user</br>
property :company_name, on: :company, from: :name</br>

validates :name, presence: true</br>
validates :email, presence: true</br>
validates :company_name, presence: true</br>

def save</br>
  super</br>
  model[:user].update(company: model[:company])</br>
end</br>
end</code></pre>

It will have the following view:

<pre><code>= simple_form_for @form do |f|</br>
- f.input :name</br>
- f.input :email</br>
- f.input :company_name, label: 'Company Name'</br>
</code></pre>

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:

<pre><code>class User < ActiveRecord::Base</br>
has_many :permissions, class_name: 'User::Permission'</br>
has_many :pay_rates, class_name: 'User::PayRate'</br>
has_many :addresses, class_name: 'User::Address', dependent: :destroy</br>
has_many :licenses, class_name: 'User::License'</br>
...</br>
...</br>

accepts_nested_attributes_for :permissions, reject_if: :all_blank, allow_destroy: true</br>
accepts_nested_attributes_for :pay_rates, reject_if: :all_blank, allow_destroy: true</br>
accepts_nested_attributes_for :addresses, reject_if: :all_blank, allow_destroy: true</br>
accepts_nested_attributes_for :licenses, reject_if: :all_blank, allow_destroy: true</br>
...
...

def build_dependencies</br>
  self.permissions ||= User::Permission.new</br>
  permissions.build_dependencies</br>

  self.pay_rates ||= User::PayRate.new</br>
  pay_rates.build_dependencies</br>

  self.licenses ||= User::License.new</br>
  licenses.build_dependencies</br>

  self.addresses ||= User::Address.new</br>
  addresses.build_dependencies</br>
  ...</br>
  ...</br>
end</br>
end<code></pre>

Now, it is time to see what we have in the controller:

<pre><code>params.require(:user).permit(:first_name, :last_name, :sex, :ssn, :primary_phone,</br>
                           :secondary_phone, :fax, :ethnicity, { ethnicity: [] },</br>
                           :email, :password, :password_confirmation, :birth_date, :date, :mi, :suffix,</br>
                           :role_id, :password, :password_confirmation, :preferred_contact, :primary_language,</br>
                           :other_language, :marital_status, :religion,</br>
                           { permissions_attributes: [:human_resources, :payroll,</br>
                                                      :clinical_assignments, :patient_schedule,</br>
                                                      :referral, :md_orders,</br>
                                                      :visit_note, :chha_assignment,</br>
                                                      :chha_supervisory_visit_note, :lvn_supervisory_visit_note,</br>
                                                      :case_conference, :progress_report, :plan_of_care,</br>
                                                      :medication_profile, :incident_report,</br>
                                                      :infection_report, :discharge_summary,</br>
                                                      :discharge_instruction, :notice_of_non_coverage,</br>
                                                      :hhabn, :visit_note_co_sign, :md_orders_co_sign] },</br>
                            { pay_rates_attributes:  [:_destroy, :id, :user_id, :task, :rate, :pay_category] },</br>
                            { licenses_attributes:   [:_destroy, :id, :name, :number, :authority,</br>
                                                      :valid_from, :valid_to, :alert, :user_id] },</br>
                            { addresses_attributes:  [:_destroy, :id, :user_id, :main_address, :secondary_address, :state,</br>
                                                      :city, :zip_code, :addressable_id, :addressable_type,</br>
                                                      :special_instruction, :residence_type] })</code></pre>

Looks not really good, right?

Look what happens if we move validations and nesting objects to the FormObject:

<pre><code>class User::Permission < ActiveRecord::Base</br>
belongs_to :user</br>
...</br>
...</br>
end</br>class User::PermissionFormObject < Reform::Form</br>
property :human_resources</br>
property :payrol</br>
property :clinical_assignments</br>
property :patient_schedule</br>
property :referral</br>
property :md_order</br>
property :visit_note</br>
property :chha_assignmen</br>
property :chha_supervisory_visit_note</br>
property :lvn_supervisory_visit_not</br>
property :case_conference</br>
property :progress_report</br>
property :plan_of_car</br>
property :medication_profile</br>
property :incident_repor</br>
property :infection_report</br>
property :discharge_summar</br>
property :discharge_instruction</br>
property :notice_of_non_coverag</br>
property :hhabn</br>
property :visit_note_co_sign</br>
property :md_orders_co_sign</br>

validate_presence_of :human_resources, :payroll,</br>
                     :clinical_assignments, :patient_schedule,</br>
                     :referral, :md_orders,</br>
                     :visit_note, :chha_assignment,</br>
                     :chha_supervisory_visit_note, :lvn_supervisory_visit_note,</br>
                     :case_conference, :progress_report, :plan_of_care,</br>
                     :medication_profile, :incident_report,</br>
                     :infection_report, :discharge_summary,</br>
                     :discharge_instruction, :notice_of_non_coverage,</br>
                     :hhabn, :visit_note_co_sign, :md_orders_co_sign</br>

# More complex validations for permissions</br>
...</br>
...</br>
...</br>
end</code></pre>

Then, let us consider the UserForm:

<pre><code>class UserForm < Reform::Form</br>
# user properties</br>

collection :permissions, form: User::PermissionForm, populate_if_empty: User::Permission</br>
collection :pay_rates, form: User::PayRateForm, populate_if_empty: User::PayRate</br>
collection :licenses, form: User::LicenseForm, populate_if_empty: User::License</br>
collection :addresses, form: User::AddressForm, populate_if_empty: User::Address</br>

# user validations</br>
end</code></pre>

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.

Oops! Something went wrong while submitting the form. Please, check the entered values.

Latest Articles

Development
Why Choose Ruby on Rails for MVP Development
Startups
Lean Startup as a Core Strategy: Short Review & Tips
Insights
Why Business Analysis Is a Treasure Trove in Software Development Projects

Subscribe via email and know it all first!

Privacy Policy

Subscribe via email and know it all first!

Thank you for your contact
Oops! Something went wrong while submitting the form. Please, check the entered values.