nested_form, collection_select, accepts_nested_attributes_for and fields_for, and adding records to a join table
Update: according to the stack trace, posted at the end of this question, I think the real problem is to figure out what attr_accessible and build association settings I need to get the contributors and linkers attributes to update with the new isbn id. Please help me!
Following on from this question...
I've got a join table between two models - isbns and contributors - which have a many to many relationship. I want to use the existing v开发者_StackOverflowalues from the contributor model in a collection_select drop-down, within a nested form using fields_for.
So when I'm creating a new isbn, I don't want to create new contributor records - I want to be able to select existing ones. And I want to be able to select many contributors, which is why I'm using the nested_form gem which allows me to dynamically create additional form fields for contributors.
My question: should I be calling the method on the join table? And if so, why do I get a 'missing block' error despite having pretty much everything attr_accessible and accepts_nested_attributes_for:
isbn.rb:
attr_accessible :linkers_attributes, :contributors_attributes, :contributor_id,
has_many :linkers
has_many :contributors, :through => :linkers
accepts_nested_attributes_for :contributors
accepts_nested_attributes_for :linkers
contributor.rb:
attr_accessible :linkers_attributes, :isbns_attributes
has_many :linkers
has_many :isbns, :through => :linkers
accepts_nested_attributes_for :isbns
accepts_nested_attributes_for :linkers
linker.rb:
belongs_to :isbn
belongs_to :contributor
accepts_nested_attributes_for :contributor
accepts_nested_attributes_for :isbn
attr_accessible :isbn_id, :contributor_id, :isbns_attributes, :contributors_attributes, :contributor_attributes, :isbn_attributes
isbns controller:
def new
@isbn = Isbn.new
@title = "Create new ISBN"
1.times {@isbn.linkers.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>
_fields: (option 1)
<%= f.fields_for :contributors do |contributor_form| %>
<li>
<%= contributor_form.label '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 %>
This creates a new ISBN, and a new record in linkers with the correct isbn_id, but also a new record in contributors, when I actually want the new linker record to contain an existing contributor_id. So I figured I'd do this:
_fields (option 2)
<%= field_set_tag 'Contributor' do %>
<li>
<%= f.label 'Contributor Sequence Number' %>
<%= f.text_field :descriptivedetail_contributor_sequencenumber%>
<%= tooltip(:xxx, :hover) %>
</li>
<%= f.fields_for :linkers do |contributor_form| %>
<li>
<%= contributor_form.label '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 %>
But this returns a missing block error.
Huge thanks in advance, I've spent two solid days on this and think I might pop.
update: Here's a clue, the stack trace from this new code, because I figured you can't actually call a method on a join table:
<%= f.fields_for :contributors do |contributor_form| %>
<li>
<%= contributor_form.collection_select(:id, Contributor.all, :id, :personnameinverted, :include_blank => true ) %>
Started POST "/isbns" for 127.0.0.1 at 2011-06-08 20:12:48 +0100
Processing by IsbnsController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"u0TszLESOnZlr4iEiBlVg6w9B5T+EH0dxJg/0E6PKuQ=", "isbn"=>{"descriptivedetail_titledetail_titleelement_titleprefix"=>"", "descriptivedetail_titledetail_titleelement_titlewithoutprefix"=>"qqq", "istc_id"=>"471", "descriptivedetail_contributor_sequencenumber"=>"", "contributors_attributes"=>{"0"=>{"id"=>"1", "_destroy"=>"false"}, "1307560328932"=>{"id"=>"14", "_destroy"=>"false"}}, "descriptivedetail_contributor_contributorrole"=>""}, "commit"=>"Create"}
User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = 2 LIMIT 1
Contributor Load (0.3ms) SELECT "contributors".* FROM "contributors" INNER JOIN "linkers" ON "contributors".id = "linkers".contributor_id WHERE "contributors"."id" IN (1, 14) AND (("linkers".isbn_id = NULL))
Completed in 308ms
So it looks like the contributor IDs are actually being set, but the linker table isn't being passed the isbn id. Onwards...
Well. Here's how I did it.
Installed cocoon gem. Finally got round to installing Haml, too,
I split out the new and update action, so the new form is just a quickie to get the id set, with the bare minimum of fields. Then users can edit it, and add contributors. Not actually bad from the user point of view as they can create an isbn quickly without having to fill in a bazillion fields.
isbns/new.html.haml
%td.main
= semantic_form_for @isbn do |f|
%h1 Create new ISBN
= render 'shared/error_messages', :object => f.object
= render 'fields', :f => f
%div#actions
= f.submit "Create"
isbns/_fields.html.haml
- f.inputs do
= f.input :istc_id, :as => :select, :collection => Istc.all
= f.input :descriptivedetail_titledetail_titleelement_titlewithoutprefix
%h3 Contributors
#tasks
= f.semantic_fields_for :linkers do |linker|
= render 'linker_fields', :f => linker
.links
= link_to_add_association 'add contributor', f, :linkers
-f.buttons do
= f.submit 'Save'
isbns/_linker_fields.html.haml
.nested-fields
= f.inputs do
= f.input :contributor_id, :label_method => :keynames, :as => :select, :collection => Contributor.all
= link_to_remove_association "remove contributor", f
Isbn.rb
has_many :linkers
has_many :contributors, :through => :linkers
accepts_nested_attributes_for :contributors, :allow_destroy => true
accepts_nested_attributes_for :linkers, :allow_destroy => true
contributor.rb
attr_accessible :linkers_attributes, :isbns_attributes
has_many :isbns, :through => :linkers
has_many :linkers
accepts_nested_attributes_for :linkers
linker.rb
belongs_to :contributor
belongs_to :isbn
accepts_nested_attributes_for :contributor
Results in
Started POST "/isbns/58" for 127.0.0.1 at 2011-06-09 12:34:14 +0100
Processing by IsbnsController#update as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"u0TszLESOnZlr4iEiBlVg6w9B5T+EH0dxJg/0E6PKuQ=", "isbn"=>{"istc_id"=>"360", "descriptivedetail_titledetail_titleelement_titlewithoutprefix"=>"thurs title", "linkers_attributes"=>{"0"=>{"contributor_id"=>"3", "_destroy"=>"", "id"=>"68"}, "1"=>{"contributor_id"=>"71", "_destroy"=>"", "id"=>"69"}, "2"=>{"contributor_id"=>"72", "_destroy"=>"", "id"=>"70"}, "3"=>{"contributor_id"=>"3", "_destroy"=>"", "id"=>"71"}}}, "commit"=>"Save", "id"=>"58"}
User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = 2 LIMIT 1
Isbn Load (8.2ms) SELECT "isbns".* FROM "isbns" WHERE "isbns"."id" = 58 ORDER BY descriptivedetail_titledetail_titleelement_titlewithoutprefix LIMIT 1
Linker Load (0.3ms) SELECT "linkers".* FROM "linkers" WHERE "linkers"."id" IN (68, 69, 70, 71) AND ("linkers".isbn_id = 58)
AREL (0.4ms) UPDATE "linkers" SET "contributor_id" = 3, "updated_at" = '2011-06-09 11:34:14.509943' WHERE "linkers"."id" = 71
Contributor Load (158.3ms) SELECT "contributors".* FROM "contributors"
Redirected to http://localhost:3000/isbns/58
Completed 302 Found in 545ms
In other words, from an isbn edit form which has dynamically produced nested fields_for, using formtastic, I can use a drop down menu to select an existing contributor which has a many to many relationship with isbns.
Phew.
精彩评论