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.


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


class Tag < ActiveRecord::Base
has_many :post_taggings, foreign_key: :tag_id, dependent: :destroy
has_many :posts, through: :post_taggings, source: :post

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

posts_controller.rb (abbreviated)

class PostsController < ApplicationController
def new
@post = Post.new

def new_post_params
params.require(:post).permit(:title, post_taggings_attributes: { :tag_id => [] })

def update_post_params
params.require(:post).permit(:title, post_taggings_attributes: [ { :tag_id => [] },
:id, :_destroy ])


<%= 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 %>


<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>

When I save the form I get the following:

Started POST "/posts" for 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 => [] }




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 => []])

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.