Skip to content

Cory Foy

Organizational agility through intersecting business and technology

Menu
  • FASTER Fridays
  • Mapping Mondays
  • Player Embed
  • Search Videos
  • User Dashboard
  • User Videos
  • Video Category
  • Video Form
  • Video Tag
Menu

Multiselect control in Rails with a Many to Many relationship

Posted on February 15, 2008 by Cory Foy

This evening I had the chance to dive back into some Rails stuff. I came across a problem of which there was surprisingly no straightforward documentation from start to finish, so I wanted to put it here.

Let’s say you have 3 tables – restaurants (id, name), categories (id, name), and restaurants_categories (restaurant_id, category_id). You want to let people who are entering or editing a restaurant select multiple categories the restaurant will belong to. Straightforward, right?

Actually, somewhat. There are a couple of good articles which get us close. The key is setting up the relationships, and combining select_tag with options_for_select.

First, after you’ve generated your models and controllers for restaurant and category, open up your model for Restaurant and add has_and_belongs_to_many :categories. This is the magic to let Rails know about the relationship.

Next, go into your restaurants_controller and in your create and update methods, just after you new up the object, add @restaurant.categories = Category.find(@params[:category_ids]) if @params[:category_ids]. category_ids is what our multiselect options box will put the selected items into.

Up till now, all of this is in the jrhicks.net blog. However, to implement the categories, he spits out HTML. There’s a better way – using select_tag with options_for_select. Open up your restaurants\_form.rhtml file, and add the following:

<%= select_tag("category_ids[]", options_for_select(Category.find(:all).collect { |cat| [cat.name, cat.id] }, @restaurant.categories.collect { |cat| cat.id}), {:multiple=>true, :size=>6})%>

Whew! Let’s break this down:

  • label – This is just the title of the select list.
  • select_tag – this follows the format (object, options, html_options)
    • “category_ids[]” – the variable that we’ll populate when the form is submitted. It has to end with “[]” to let Rails know we are populating into an array
    • options_for_select – This is a helper which spits out the options select_tag is looking for. It has two parameters – an array of objects to use for the options list, and an array of item ids that should be selected
    • {:multiple=>true,:size=>6} – This sets our select list to allow multiple selections, and show 6 entries at a time

Going back to the options_for_select – Notice we do two things:

Category.find(:all).collect { |cat| [cat.name, cat.id] }

This loops through all of the categories and builds an array with their names and ids. This will be turned into basically.

Second, we do:

@restaurant.categories.collect { |cat| cat.id }

which loops through all of the categories we have set in the restaurant, passing an array of their ids as the second parameter.

So effectively, this displays the multiselect list, and automatically selects the appropriate options if any have been selected.

14 thoughts on “Multiselect control in Rails with a Many to Many relationship”

  1. Scott Bellware says:
    February 15, 2008 at 1:08 am

    Cory,

    It’s usually considered bad practice to data access calls from the view. It’s much, much harder to document the data access through TDD specifications since some of the data access is in the view, and testing views requires a different kind of testing.

  2. Cory Foy says:
    February 15, 2008 at 8:53 am

    Thanks Scott. The way to get around that would simply be to populate an @categories variable and an @selected_categories variable in the control that the view would use.

    Thanks for the reminder!

  3. Eric says:
    March 24, 2008 at 9:56 am

    Great little article! Helped me out, thanks!

  4. animeworld says:
    November 8, 2008 at 9:35 am

    Hi folks!
    I got the following error:

    You have a nil object when you didn't expect it!
    You might have expected an instance of ActiveRecord::Base.
    The error occurred while evaluating nil.[]
    app/controllers/restaurants_controller.rb:44:in `create'

    Parameters:

    {"restaurant"=>{"name"=>"Restaurant do Leandro"},
    "commit"=>"Create",
    "authenticity_token"=>"bdad0a7ac7b929c1200fc2e48f997f5e86eb87a1",
    "category_ids"=>["1"]}

    someone could help me?

  5. Thomas Rekittke says:
    April 4, 2009 at 12:21 pm

    Nice article. There is another way for multiselect (for has_many and habtm relationships) though – showing two select boxes, one for all available options and one for the selected options, and the opportunity to move options from one select box to the other. I call this SwapSelect and is described (incl. download) here: http://trendwork.kmf.de/175

  6. Anonymous says:
    April 22, 2009 at 3:13 am

    I know, it’s a 1y old post. Anyway i’m sure a lot of visitors drop in this article so…

    @animeword
    In your create and update method change “@params” in “params” (no @ is needed)

  7. Dmitry Polushkin says:
    November 15, 2009 at 4:48 am

    Hi, you can change @restaurant.categories.collect { |cat| cat.id}) into @restaurant.category_ids. Also, I think it’s better to assign this values to variables in controller. :)

    Anyway thanks for this post.

  8. Family Sociology says:
    March 23, 2010 at 8:31 am

    I just wanted to take a moment and let you know that I’ve been savouring reading your posts over the last few weeks. I have a website of my own, and would enjoy to switch links with you. If you’re interested just leave me a comment on my page or send me an e-mail with your details.

  9. Srikanth says:
    June 1, 2011 at 10:10 am

    Works like charm! Thanks a lot … !

  10. Sandeep Arneja says:
    October 8, 2011 at 3:02 pm

    Hi,

    You have:
    @restaurant.categories = Category.find(@params[:category_ids]) if @params[:category_ids]

    Instead I think it should be:

    @restaurant.category_ids = Category.find(@params[:category_ids]) if @params[:category_ids]

    Above, I have changed @restaurant.categories to @restaurant.category_ids

    I am a newbie to rails and ruby both, so please pardon me if i am making an obvious mistake.

    thx

  11. Anup Mahbudhe says:
    February 3, 2012 at 2:28 am

    Thanks Cory,

    Great post , even the above technique worked for me after some small changes on rails 3.1.

  12. Taimoor changaiz says:
    March 1, 2012 at 12:50 am

    Very nice to share your ideas and knowledge to others :)

    Well done Cory :)

  13. Franziska says:
    September 8, 2012 at 10:38 am

    Thank you very much for this, I searched for this kind of form very long and was really desperate because nothing worked! :)

  14. James says:
    February 14, 2013 at 3:32 pm

    I want to say thanks, you ended a 4 hour search with your one line solution :)

Comments are closed.

© 2025 Cory Foy | Powered by Superbs Personal Blog theme