I'm new to rails and I'm having trouble saving nested attributes of a join table using collection_select. I have models post, tag, and post_tagging. post_tagging is a join table.
I want to set multiple tags per post so I attempted to use a multi-select via collection_select, but when I save only the post_id is inserted into the database. Below is my code and the log.
Post.rb
class Post < ActiveRecord::Base
has_many :post_taggings, foreign_key: :post_id, dependent: :destroy
has_many :tags, through: :post_taggings, source: :tag
accepts_nested_attributes_for :post_taggings, reject_if: :all_blank, allow_destroy: true
end
Tag.rb
class Tag < ActiveRecord::Base
has_many :post_taggings, foreign_key: :tag_id, dependent: :destroy
has_many :posts, through: :post_taggings, source: :post
end
post_tagging.rb (I turned off presence validation on tag_id and post_id in the post_tagging model so I could get a log of the POST.)
class PostTagging < ActiveRecord::Base
belongs_to :post
belongs_to :tag
#validates :post_id, presence: true
#validates :tag_id, presence: true
end
posts_controller.rb (abbreviated)
class PostsController < ApplicationController
def new
@post = Post.new
@post.post_taggings.build
end
def new_post_params
params.require(:post).permit(:title, post_taggings_attributes: { :tag_id => [] })
end
def update_post_params
params.require(:post).permit(:title, post_taggings_attributes: [ { :tag_id => [] },
:id, :_destroy ])
end
end
views/post/new.html.erb
<%= form_for(@post) do |f| %>
<%= f.fields_for :post_taggings do | pt | %>
<%= pt.label :post_taggings, "Tags" %><br />
<%= pt.collection_select(:tag_id, Tag.all, :id, :name, {include_hidden: false}, {multiple: true} ) %><br />
<% end %>
The HTML
<select id="post_post_taggings_attributes_0_tag_id" multiple="multiple" name="post[post_taggings_attributes][0][tag_id][]">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
<option value="5">5</option>
<option value="6">6</option>
<option value="7">7</option>
<option value="8">8</option>
<option value="9">9</option>
<option value="10">10</option>
</select>
When I save the form I get the following:
Started POST "/posts" for 127.0.0.1 at 2014-12-13 04:22:19 -0800
Processing by PostsController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"DaeMJb5b4PcLUz2YfQCjYk1r7pzcMd3NOmhYwEExz2U=", "post"=>{"title"=>"The Title", "post_taggings_attributes"=>{"0"=>{"tag_id"=>["1", "2", "6"]}}}, "commit"=>"Create Post"}
(0.1ms) begin transaction
SQL (0.5ms) INSERT INTO "posts" ("created_at", "title", "updated_at") VALUES (?, ?, ?) [["created_at", "2014-12-13 12:22:19.789055"], ["title", "The Title"], ["updated_at", "2014-12-13 12:22:19.789055"]]
SQL (0.4ms) INSERT INTO "post_taggings" ("created_at", "post_id", "updated_at") VALUES (?, ?, ?) [["created_at", "2014-12-13 12:22:19.791928"], ["post_id", 16], ["updated_at", "2014-12-13 12:22:19.791928"]]
(2.2ms) commit transaction
Redirected to http://localhost:3000/posts/16
Completed 302 Found in 27ms (ActiveRecord: 3.3ms)
Since it's not working I know I'm doing something wrong. I'm also not confident that the edit case will work.
I feel I'm close since it works with a single select if I change the strong params from
{ :tag_id => [] }
to
:tag_id
I prefer to do it more convenience way.
# in your form
<%= form_for(@post) do |f| %>
## your other fields
<%= f.collection_select(:tag_ids, Tag.all, :id, :name, {include_hidden: false}, {multiple: true} ) %><br />
<% end %>
#in your controller
def post_params
params.require(:post).permit([:title, :tag_ids => []])
end
now instead of two different permitted param list, things will work for both. Removing tag will work without complexity.
Please replace your permitted params values in desired places in your actions.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With