There are lots of good questions and answers here and on the interweb about getting nested_form, collection_select, accepts_nested_attributes_for and fields_for to play nicely together, but I'm still stumped. Thanks in advance if you can help me.
Aim: To generate a new isbn record. An isbn can have many contributors. I am successfully using the ryanb nested_form gem to dynamically produce new contributor fields on a form, as required. One of these fields uses a collection_select drop down of all the name records in Contributor. When the new record is created, the many contributor ids need to be written to the join table (contributors_isbns).
I have got bits of this working, but only to the point where I can save a single contributor ID to the new record in the isbns table. I can't seem to get anywhere in writing any data to the join table.
I have three models. Contributors and Isbns have a many to many relationship, which I've done using has_many :through. An isbn can have many contributors, and a contributor can have many isbns. They are joined via contributors_isbn.
isbn.rb
attr_accessible :contributor_id
has_many :contributors, :through => :contributors_isbns
has_many :contributors_isbns
accepts_nested_attributes_for :contributors
accepts_nested_attributes_for :contributors_isbns
contributor.rb
attr_accessible :isbn_id
has_many :contributors_isbns
has_many :isbns, :through => :contributors_isbns
accepts_nested_attributes_for :isbns
contributors_isbn.rb
class ContributorsIsbn
attr_accessible :isbn_id, :contributor_id
belongs_to :isbn
belongs_to :contributor
accepts_nested_attributes_for :contributors
In the isbns controller:
def new
@isbn = Isbn.new
@title = "Create new ISBN"
1.times {@isbn.contributors.build}
@isbn.contributors_isbns.build.build_contributor
end
(obviously I can't make my mind up on which build method to use.)
In the isbns new.html.erb view:
<%= nested_form_for @isbn, :validate => false do |f| %>
<h1>Create new ISBN</h1>
<%= render 'shared/error_messages', :object => f.object %>
<%= render 'fields', :f => f %>
<div class="actions">
<%= f.submit "Create" %>
</div>
<% end %>
In the _fields partial, a version with a very plain text_field:
<%= field_set_tag 'Contributor' do %>
<%= f.link_to_add "Add Additional Contributor", :contributors %>
<li>
<%= f.label 'Contributor Sequence Number' %>
<%= f.text_field :descriptivedetail_contributor_sequencenumber%>
</li>
<%= f.fields_for :contributors_isbns, :validate => false do |contrib| %>
<li>
<%= contrib.label :id, 'contributors_isbns id' %>
<%= contrib.text_field :id %>
</li>
<% end %>
<li>
<%= f.label 'Contributor Role' %>
<%= f.text_field :descriptivedetail_contributor_contributorrole %>
</li>
<% end %>
And here, a fancier version which doesn't work either:
<%= f.fields_for :contributors_isbns, :validate => false do |contributors| %>
<li>
<%= f.label :personnameinverted, 'Contributor Name' %>
<%= f.collection_select(:contributor_id, Contributor.all, :id, :personnameinverted ) %>
</li>
<% end %>
This code uses the answer from here. Both result in a 'Missing block" error on the nested_form_for @isbn
line.
Thanks so much again in advance.
Update: here is some info about the nested_form gem which might come in handy for looking at this sort of problem. And here's a [2009 but still relevant post][4] on accepts_nested_attributes_for.
Update 2: well, here's a thing. I've been poking around on a cut-down version of this in two different models, not using collection_select or has_many through, but just on a simple belongs_to / has_many association. The parent model is Contract and the child model is Istc. I couldn't even create a record through the nested form. However, after looking in the stack and googling the error message "Warning. Can't mass-assign protected attributes" I've just added :istcs_attributes
to my :attr_accessible
line and now I can add records. A rather crucial bit missing, and a case of RTFM, as it's right there in the gem readme. I'll update later to see if this works on the more complicated has_many through association.
Update 4: [Here][5] is another useful post about how to deal with a nil record error message.
Update 5: Slight detour - When I dynamically added a new set of fields to the form, one one of the child records was being created. Duh - I had the "Add" link inside the child forms area. Here's the before:
<%= f.fields_for :istcs do |istc_form| %>
<h4> Istc</h4>
<%= istc_form.label "istc name" %>
<%= istc_form.text_field :title_title_text %>
<%= istc_form.link_to_remove "[-] Remove this istc"%>
<%= f.link_to_add "[+] Add an istc", :istcs %>
<% end %>
and here's the after:
<%= f.fields_for :istcs do |istc_form| %>
<h4> Istc</h4>
<%= istc_form.label "istc name" %>
<%= istc_form.text_field :title_title_text %>
<%= istc_form.link_to_remove "[-] Remove this istc"%>
<% end %>
<%= f.link_to_add "[+] Add an istc", :istcs %>
Update 6, post-answer:
Oh noes. The collection_select isn't working. It's adding new contributor records, not using an existing one from the contributor model. Someone else had this problem too. Any ideas?
Huzzah! Here's the code which makes all this work. Bit verbose but didn't want to leave anything out. My main learnings:
you need to make the child attributes attr_accessible in the parent model
you need to make the parent and child ids attr_accessible in the join table model
it makes life easier if you build at least one child instance in the parent controller.
contributor.rb model
class Contributor < ActiveRecord::Base
attr_accessible #nothing relevant
has_many :contributors_isbns
has_many :isbns, :through => :contributors_isbns
isbn.rb model
class Isbn < ActiveRecord::Base
attr_accessible :contributors_attributes, :contributor_id, :istc_id #etc
belongs_to :istc
has_many :contributors, :through => :contributors_isbns
has_many :contributors_isbns
accepts_nested_attributes_for :contributors #if you omit this you get a missing block error
contributors_isbn model
class ContributorsIsbn < ActiveRecord::Base
belongs_to :isbn
belongs_to :contributor
attr_accessible :isbn_id, :contributor_id
isbn controller
def new
@isbn = Isbn.new
@title = "Create new ISBN"
1.times {@isbn.contributors.build}
end
new.html.erb
<td class="main">
<%= nested_form_for @isbn, :validate => false do |f| %>
<h1>Create new ISBN</h1>
<%= render 'shared/error_messages', :object => f.object %>
<%= render 'fields', :f => f %>
<div class="actions">
<%= f.submit "Create" %>
</div>
<% end %>
_fields.html.erb
<%= field_set_tag 'Identifier Details' do %>
<li>
<%= f.label 'Title prefix' %>
<%= f.text_field :descriptivedetail_titledetail_titleelement_titleprefix %>
</li>
<li>
<%= f.label 'Title without prefix' %>
<%= f.text_field :descriptivedetail_titledetail_titleelement_titlewithoutprefix %>
</li>
<li>
<%= f.label 'ISTC' %>
<%= f.collection_select(:istc_id, Istc.all, :id, :title_title_text, :prompt => true) %>
</li>
<% end %>
<%= field_set_tag 'Contributor' do %>
<li>
<%= f.label 'Contributor Sequence Number' %>
<%= f.text_field :descriptivedetail_contributor_sequencenumber%>
</li>
<%= f.fields_for :contributors, :validate => false do |contributor_form| %>
<li>
<%= contributor_form.label :personnameinverted, 'Contributor Name' %>
<%= contributor_form.collection_select(:isbn_id, Contributor.all, :id, :personnameinverted ) %>
</li>
<%= contributor_form.link_to_remove "[-] Remove this contributor"%>
<% end %>
<%= f.link_to_add "[+] Add a contributor", :contributors %>
<li>
<%= f.label 'Contributor Role' %>
<%= f.text_field :descriptivedetail_contributor_contributorrole %>
</li>
<% end %>
来源:https://stackoverflow.com/questions/6270367/rails-3-nested-form-collection-select-accepts-nested-attributes-for-and-field