views:

219

answers:

1

Hello all,

I have two models: Company and Person

class Person < ActiveRecord::Base
 belongs_to :company
end

class Company < ActiveRecord::Base
  has_many :people
  accepts_nested_attributes_for :people, :allow_destroy => true, :reject_if => proc {|attrs| attrs.all? {|k,v| v.blank? } }
end

And my HTML form partial for new and edit actions looks like this:

<% form_for(@company) do |company_f| %>
  <p>
    <b>Name</b><br />
    <%= company_f.text_field :name %>
  </p>

  <ul>
    <%= render :partial => 'person_fields', :collection => @company.people, :locals => {:company_f => company_f} %>
    <%= link_to_add_fields(:people, company_f) %>
  </ul>

  <p>
    <%= company_f.submit "Submit" %>
  </p>
<% end %>

where "_person_fields" partial looks like this:

<li>
  <% company_f.fields_for :people, person_fields do |person_f| %>
    <%= person_f.label :name %>
    <%= person_f.text_field :name %>
  <% end %>
</li>

At the moment, if I typed in person_f.text_fiel :name the name of the person, and hit save, a new Person model with that name gets created. Not what I want at all, I already HAVE that person's Person model in the database, I rather want to ASSOCIATE this person to this company.

Another thing is that I wouldn't mind using the name for human-friendly identification of the person rather than id like this for the "_person_fields" partial

<li>
  <% company_f.fields_for :people, person_fields do |person_f| %>
    <%= person_f.label :id %>
    <%= person_f.text_field :id %>
  <% end %>
</li>

this by the way, doesn't work either. when I hit submit, nothing happens. nothing gets saved or changed or anything.

So I thought, just for the sake of experiment, say I did use id's for identification for a Person model, (so that I don't have to go in to autocomplete with a hidden id field which I am using for another project. I hate it). All I want is: go to a new/edit Company page, there's a bunch of textfields for me to type in ids of people, and save and then these people are then associated with the company. I mean, it's exactly like

people = Person.find(1,2,3)
#=>["romeo","juliet","henry"]
company = Company.first
#=>["Shakespeare Co."]
company.people<<people
company.people
#=>["romeo","juliet","henry"]

And it'd be best if I didn't have to use select menus because eventually if the project takes off and I have a thousand people, that's too big for any select menu. I know then I will have to use autocomplete + hidden id field that gets set when a person's name is chosen.

Thanks!!

+1  A: 

accepts_nested_attributes_for :people defines people_attributes and people_attributes= methods in your Company model. Those two methods are used when you have fields_for :people in form. As stated in documentation (read whole page, not only method definition):

Nested attributes allow you to save attributes on associated records through the parent.

It doesn't work to associate two objects, that can be done without it, you still have people method in Company.

Formtastic for example uses select with multiple attribute (you can check more than one option with ctrl) or list of check_boxes (easier for normal user, don't have to touch keyboard).

If you want to use autocomplete, it could be done, but you need to append your ids to people[] array (don't remember format right now, I'll check later. You can check its format when you create attribute in your form for :people I think)

Edit:
I think I know hot to do it simple way (without autocomplete, simple html only).

In your companies/edit (or new) view place:

<p>
  <%= company_f.label :people %>
  <%= company_f.collection_select :person_ids, Person.all, :id, :name, {}, {:multiple => true} %>
</p>

That will allow you to select multiple people to company (with Ctrl). Params generated by this part transfered to your controller should now look like:

"company"=>{"name"=>"CompanyA", "person_ids"=>["1", "3"]}, "commit"=>"Update", "id"=>"4"

If you want to show people with checkboxes I managed to get it with this (I copied part of markup from formtastic):

<ul>
  <% Person.all.each do |person| %>
    <li>
      <%= check_box :person, :id, {:name => "company[person_ids][]", :checked => @company.people.include?(person) }, person.id, nil %>
      <%= person.name %>
    </li>
  <% end %>
</ul>

I had no time today to check how to do it with hidden fields, so you can use autocomplete, but I believe that it should be similar to checkboxes - you need to use something which adds <input type="hidden" name="company[person_ids][]" value="#{person.id}"> for every autocompleted person, and also need to create initial list of people already in company (YOU OVERRIDE THE LIST, not add to list).


If you want field to add or remove (separate fields, sorry) people from company, then this should work:

in Company model:

def add_people=(people_ids)
  ids = people_ids.split(/,/).map(&:to_i)
  person_ids += ids if ids
end
def add_people
  ""
end
def remove_people=(people_ids)
  ids = people_ids.split(/,/).map(&:to_i)
  person_ids -= ids if ids
end
def remove_people
  ""
end

and in your new/edit view:

<p>
  <%= f.label :add_people %><br />
  <%= f.text_field :add_people %>
</p>
<p>
  <%= f.label :remove_people %><br />
  <%= f.text_field :remove_people %>
</p> 

Now all you need to do is to find JavaScript for auto-complete, connect it to ids and names of all people (probably PeopleController#index, :format => :json should be good) and tell it to fill those text_fields (can be hidden fields if you would use autocomplete).

This approach should work, because you define virtual attributes in company model, and by assigning to them string in format "1, 2, 6", you add/remove those ids from your collection of associated people

MBO
Does that mean you have to use form_tag or text_field_tag instead of company_f.text_field for the "people_ids[]"?Because we might have many contractors and selecting the from a drop down menu or check box isn't very efficient.
Nik
@Nik I updated my answer
MBO
Hey MBO thanks for the solution for the adding and removing person in the company's new/edit form. I thought of that but in a much, much less specific way. So thank you for materializing it here alongside your detailed walkthrough.
Nik