4

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
Justin
  • 43
  • 1
  • 4

1 Answers1

13

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.

Rubyrider
  • 3,567
  • 1
  • 28
  • 34
  • 2
    You need to permit `:tag_ids => []` instead of `:tag_ids`. – Nermin Dec 13 '14 at 13:54
  • @Rubyrider thanks for the answer, when I update the form to :tag_ids and create/edit a post I get: `undefined method tag_ids' for #` – Justin Dec 16 '14 at 06:49
  • tags_ids also results in an error. Am I missing something else? Are my relations in the Model incorrect? – Justin Dec 16 '14 at 07:09
  • Oh God! Please put it into f form object which is post. It should only work for post object. – Rubyrider Dec 16 '14 at 07:12
  • I have updated my answer. checkout the form in my example. And its tag_ids, not tags_ids, my apologize . – Rubyrider Dec 16 '14 at 07:46
  • I'm not sure if I understood correctly. I changed the `f.fields_for :post_taggings` to `f.fields_for :post` and the error message went away. Now when I save however I get unpermitted params. When I check the html the Select tag's name is `post[post][tags_ids][]` It seems odd to me that the post parms would contain another post. – Justin Dec 16 '14 at 08:04
  • Fields for is not needed here. Its a part of @post object – Rubyrider Dec 16 '14 at 08:27