Rails 3: Multiple Select with has_many through associations

Ruby on-RailsRuby on-Rails-3

Ruby on-Rails Problem Overview


I want to get possibility to select several Categories for one Post with multiple select.

I have next models: Post, Category and PostCategory.

class Post < ActiveRecord::Base
  has_many :post_categories
  has_many :categories, :through => :post_categories
end

class Category < ActiveRecord::Base
  has_many :post_categories
  has_many :posts, :through => :post_categories
end

class PostCategory < ActiveRecord::Base
  has_one    :post
  has_one    :category
  belongs_to :post      # foreign key - post_id
  belongs_to :category  # foreign key - category_id
end

In my controller I have something like @post = Post.new . I've created some categories.

And in view I have:

<%= form_for @post do |f| %>
    <%= f.text_field :title %>
    <%= f.select :categories, :multiple => true %>
    <%= f.submit %>
<% end %>

And... where is my categories? I have only "multiple" in select options. I think it's something wrong with my form.

Ruby on-Rails Solutions


Solution 1 - Ruby on-Rails

Sorry to resurrect the dead, but I found a much simpler solution that lets one use the default controller action code and use the ActiveModel setter logic for a has_many. Yes, it's totally magic.

<%= f.select :category_ids, Category.all.collect {|x| [x.name, x.id]}, {}, :multiple => true %>

Specifically, using the :category_ids (or :your_collection_ids) param name will automagically tell Rails to call @post.category_ids = params[:post][:category_ids] to set the categories for that post accordingly, all without modifying the default controller/scaffold #create and #update code.

Oh, and it works with has_many :something, through: :something_else automatically managing the join model. Freaking awesome.

So from the OP, just change the field/param name to :category_ids instead of :categories.

This will also automatically have the model's selected categories populate the select field as highlighted when on an edit form.

References:

From the has_many API docs where I found this.

Also the warning from the form helpers guide explains this "type mismatch" when not using the proper form-field/parameter name.

By using the proper form-field/param name, you can dry up new and edit forms and keep the controllers thin, as encouraged by the Rails way.

note for rails 4 and strong parameters:

def post_params
  params.require(:post).permit(:title, :body, category_ids: [])
end

Solution 2 - Ruby on-Rails

Final solution to organize categories in your posts, I hope it will be useful.

To use multiple we need select_tag:

<%= select_tag "categories", options_from_collection_for_select(Categories.all, 'id', 'name'), :multiple => true %>

Or f.select (many thanks to Tigraine and Brent!), it's more elegant way:

<%= f.select :categories, Category.all.collect {|x| [x.name, x.id]}, {}, :multiple => true %>

In create action of our controller we need:

def create
   @post = Post.new(params[:post])

if @post.save
  
  params[:categories].each do |categories|
     categories = PostCategory.new(:category_id => categories, :post_id => @post.id)
     if categories.valid?
       categories.save
     else
       @errors += categories.errors
     end
  end
  redirect_to root_url, :notice => "Bingo!"
else
  render "new"
end
end

Solution 3 - Ruby on-Rails

What you need is a list of options for the select:

<%= f.select :category_id, Category.all.collect {|x| [x.name, x.id]}, :multiple => true %>

Solution 4 - Ruby on-Rails

Tigraine almost had it, but you need to specify an additional empty hash:

<%= f.select :category_id, Category.all.collect {|x| [x.name, x.id]}, {}, :multiple => true %>

Solution 5 - Ruby on-Rails

As the @post does not have id, the from might not display categories as there is no association. You need to pass do a build on @post something like

 @post = Post.new(:categories => Category.all)

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionOleg PaskoView Question on Stackoverflow
Solution 1 - Ruby on-RailswinfredView Answer on Stackoverflow
Solution 2 - Ruby on-RailsOleg PaskoView Answer on Stackoverflow
Solution 3 - Ruby on-RailsTigraineView Answer on Stackoverflow
Solution 4 - Ruby on-RailsBrent SowersView Answer on Stackoverflow
Solution 5 - Ruby on-RailsnarenView Answer on Stackoverflow