Justin Justin - 6 months ago 9
Ruby Question

rails 4 collection_select multiple nested attributes not saving properly

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

Answer

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.