habtm Rails 4 Example Instructions

I'm taking the learning by doing approach to Ruby and Ruby on Rails by building an application. I'm creating an app that will have a directory of various resources people can use to learn to code.

As soon as I started, I ran into trouble trying to model my data. Some fields in my directory are straight forward like name and description, but others could contain multiple pieces of data in a single field, like programming language or difficulty level. For example a resource like Codecademy has lessons in multiple languages, including Ruby. And the reverse is also true, Ruby is taught by many resources, including Codecademy. In Rails this relationship is known as a has_and_belongs_to_many association. A resource has many languages and belongs to many languages.

After banging my head for two days and combing through loads of guides, books, and Stack Overflow questions I finally figured out how to use has_and_belongs_to_many (habtm) associations in Rails 4. Here's my step-by-step approach.

In the spirit of Halloween, my example involves kids and candy. Kids get a lot of different candy for halloween and candies go to a lot of different kids. Let's get started:

Let's get started in the command line. Execute each of the following lines:

  • rails new halloween
  • cd halloween
  • rails generate scaffold kid name
  • rails generate scaffold candy name
  • rails generate migration candies_kids
  • What's happening here? First, we create a new Rails application named "halloween" and move into it. Next, we're generating scaffolds for our kid and candy models, scaffolds set up common files with reasonable default code. We don't have to use scaffolds, but this will get us further along in the app so we can just focus on creating and executing the habtm association. Finally we create the database migration file for the candies kids join table. This is where the data shared between candy and kids is stored. It's important to note when naming the join table that you list the models alphabetically, so candies_kids, not kids_candies.

    Now let's move from the command line to Sublime or the text editor of your choice. Add or edit the following lines of code in the indicated files:


    - /app/models/candy.rb
    has_and_belongs_to_many :kids

    - /app/models/kid.rb
    has_and_belongs_to_many :candies

    These lines of code declare the relationship between the models.


    - /app/controllers/candies_controller.rb

    create an instance variable in two places

    @kids = Kid.all

    in candy_params

    params.require(:candy).permit(:name, :kid_ids => [])

    - /app/controllers/kids_controller.rb

    create an instance variable in two places

    @candies = Candy.all

    in kids_params

    params.require(:kids).permit(:name, :candy_ids => [])

    This is one area where I got hung up. In Rails 3 you wouldn't need to update the controller, you would add "attr_accessible :name" to each model. However in Rails 4 we must use strong parameters, as attr_accessible has been extracted out of Rails. So in each of the controller files we add the attributes we're going to use from the opposing model, which in this case is the id field and the array of data that will be contained in it.


    Finally we need to manually edit the table we've started creating. We're going to create the candies_kids table with a field for the candy_id and a field for the kid_id. Those are the only two fields we need on the join table, so we'll tell Rails to not create an id for each entry. This is where :id => false comes into play.

    - /db/TIMESTAMP_candies_kids.rb

    def change
      create_table :candies_kids, :id => false do |t|
        t.integer :candy_id
        t.integer :kid_id

    Once you've updated the migration, go back into the command line and run the migration to create the table.

    • rake db:migrate

    The habtm relationship is now all set up. Let's look at two different ways to add data to the app. This is another area I had a hard time figuring out. Some exercises showed how to add data through the rails console, others only showed how part of one view should be set up. Here I've tried to show you a full 360 of adding and viewing the data.

    In the command line use rails console to create new kids and candies. Then we can associate candies to kids and kids to candies.

    • rails console
    • ashley = Kid.create!(:name => "Ashley")
    • snickers = Candy.create!(:name => "Snickers")
    • ashley.candies << snickers
    • timmy = Kid.create!(:name => "Timmy")
    • skittles = Candy.create!(:name => "Skittles")
    • skittles.kids << [timmy, ashley]

    In order to view, add and edit the association data from within the app we need to make some file adjustments first. The scaffolds we created earlier already took care of creating the different views we'll use to see and edit the models, but they don't include the association data between the models yet. So back into the text editor we go:


    - /app/views/kids/index.html.erb
    <td><%= kid.candies.map {|c| c.name}.join(', ') %></td>
    - /app/views/candies/index.html.erb
    <td><%= candy.kids.map {|k| k.name}.join(', ') %></td>
    - app/views/kids/show.html.erb
      <strong>Candy Names:</strong>
      <%= @kid.candies.map {|c| c.name}.join(', ') %>
    - app/views/candies/show.html.erb
      <strong>Kids Names:</strong>
      <%= @candy.kids.map {|k| k.name}.join(', ') %>

    In the index.html.erb and show.html.erb files we use the map iterator to take the needed elements out of the data set and display each of their names separated by a comma and space. So in the case of Ashley based on what we added through the rails console we would see Snickers, Skittles in the app. Note the lines in the show.html.erb files call the instance variables, while the index.html.erb files are calling local variables since they are within blocks.

    - /app/views/kids/_form.html.erb
    <div class="field">
      <%= collection_check_boxes(:kid, :candy_ids, @candies, :id, :name) %>
    - app/views/candies/_form.html.erb
    <div class="field">
      <%= collection_check_boxes(:candy, :kid_ids, @kids, :id, :name) %>                            

    In the _form.html.erb files, that are rendered in the edit and new pages, we create checkboxes for each option. We use the collection_check_boxes method to return check box tags for the collection of values. In the parentheses we include, in order, the object, method, collection, value_method, and text_method.

    Finally, back in your command line:

    • rails server

    Now in your browser go to localhost:3000/kids or localhost:3000/candies. There you'll see the data we already added through the rails console. Here you can also create new kids and candies and assign them to each other.

    And there you have it. You've just created a habtm relationship. Many thanks to Josh Cheek for his assistance with editing this post. I learned even more through the editing that I'll now use to update my own application. If you follow the steps in this post to set up a habtm relationship let me know how it goes. Did you find it clear? Is there anything you're still unsure about? I'd love to hear your thoughts.

    Until next time,

    Wright Said Fred

    comments powered by Disqus