Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rails Form Objects with multiple nested resources

7 Patterns to Refactor Fat ActiveRecord Models - here is a great article about different refactoring approaches using PORO. Under the 3rd caption there is a Form Object pattern, which I really liked and already implemented in one of the projects. There is only an example using one nested resource, but I would like to implement this pattern for multiple nested resources. Maybe someone here had already dealt with this? I don't necessarily need any code examples, just the basic idea would be fine.

Update

Consider this example. I have two models.

class Company
  has_many :users

  accepts_nested_attributes_for :users
end

class User
  belongs_to :company
end

In case of one nested user for company using Form Object Pattern I would write the following:

<%= form_for @company_form do |f| %>
  <%= f.text_field :name %>
  <%= f.text_field :user_name %>
  <%= f.submit %>
<% end %>     

Form Object

class CompanyForm
  include Virtus

  extend ActiveModel::Naming
  include ActiveModel::Conversion
  include ActiveModel::Validations

  attr_accessor :company, :user

  def user
    @user ||= company.users.build
  end

  def company
    @company ||= Company.new
  end

  def submit(params={})
    company.name = params[:name]
    user.name = params[:user_name]
    persist!
  end

  private

  def persist!
    company.save!
    user.save!
  end
end 

But what if I have a form, where a company with multiple users can be created. The usual approach is to write it like this, using nested_form:

<%= nested_form_for @company do |f| %>
  <%= f.text_field :name %>
  <%= fields_for :users, do |user_form| %>
    <%= user.form.text_field :name %>
  <% end %>
  <%= f.link_to_add "Add a user", :users %>
  <%= f.submit %>
<% end %>

What I am asking is how do I implement that Form Object Pattern in this case?

like image 470
tokenvolt Avatar asked Jul 31 '13 18:07

tokenvolt


1 Answers

the rails fields_for helper checks for a method in this format: #{association_name}_attributes=

so, if you add this method to CompanyForm:

def users_attributes=(users_attributes)
  # manipulate attributes as desired...
  @company.users_attributes= users_attributes
end

def users
  company.users
end

the fields_for generators will generate the nested users fields for a CompanyForm as if it were a Company. the above could be rewritten as a delegation since nothing is happening in the methods:

  delegate :users, :users_attributes=, :to => :company, :prefix => false, :allow_nil => false
like image 153
schpet Avatar answered Jan 19 '23 20:01

schpet