has_many :through Tutorial

______

by Matt

A step-by-step tutorial on how to make a has_many :through association in Ruby on Rails.

The has_many :through association is one of the most useful associations in Rails. It is a way of relating two models together through another model. For instance, a mentor may have many mentees and a mentee may have many mentors, all linked together through mentorships. The three models there would be mentors, mentees, and mentorships.

Or doctors may have many patients and patients may have many doctors and they could all be linked together through their appointments. In fact, our Code Fellows teachers referred to these relationships as "Ment or Ship"s.

Rails makes creating these associations very easy, but it's also easy to overthink these relationships. If you just let Rails perform its magic, though, you can really see one of the strengths of the Ruby on Rails framework. The first time I tried to make a has_many :through association, I overthought it and spent many hours trying to figure out what would later take me only about 20 minutes.

This tutorial is meant to help a Rails beginner create and understand this association and see how easy Rails can make your life! has_many :through is just one of many model associations, but can be somewhat confusing if you're not clear on the concept. I will take you step-by-step through the process of creating the association, starting with creating a new Rails project.

I used Rails version 3.2.14 with Ruby version 2.0.0p247. Follow along as we associate Jedis to Padawans through "Apprenticeships". If you're not familiar with Star Wars (?!), Jedis are like teachers and Padawans are like students. (And go now to watch Star Wars Episodes 4-6. This tutorial will be here when you get back.)

In a galaxy...

First step, create our new project, called "sithfighters". We don't need the test framework, so we'll leave that out of our project.

    $ rails new sithfighters --skip-test-unit

Next, we will create the three models we need for our has_many :through association. To make our lives easier, we'll use scaffolding in order to create the associated views and controllers at the same time.

    $ rails g scaffold jedi name:string --skip-test-framework
    $ rails g scaffold padawan name:string --skip-test-framework
    $ rails g scaffold apprenticeship jedi_id:integer padawan_id:integer --skip-test-framework

With our new models, we need to run the migrations to create the necessary tables in our database, so:

    $ rake db:migrate

And now let's make the associations. This is done in the model files. First up, jedi.rb (in app/models directory) should look like this:

    class Jedi < ActiveRecord::Base
      attr_accessible :name
      has_many :apprenticeships
      has_many :padawans, :through => :apprenticeships
    end

Notice how the syntax makes it very clear what this association is all about. Pretty cool! Then, the padawan.rb model file:

    class Padawan < ActiveRecord::Base
      attr_accessible :name
      has_many :apprenticeships
      has_many :jedis, :through => :apprenticeships
    end

And, finally, set up the apprenticeships.rb model file like this:

    class Apprenticeship < ActiveRecord::Base
      attr_accessible :jedi_id, :padawan_id
      belongs_to :jedi
      belongs_to :padawan
    end

We're almost ready to take a look at the app. Just a couple of steps before we fire it up in our browser. Delete the public/index.html file first. Then, we want the apprenticeships index page to be the root of our app, so open up config/routes.rb and add this line somewhere (toward the top is nice):

    root :to => 'apprenticeships#index'

Then, start the local server in sithfighter's main directory:

    $ rails s

Open your browser, and go to the address http://localhost:3000. This is what you should see:

Cool! Could it be that easy? Well, almost. If we click "New Apprenticeship", we get this:

(It may look a little different, depending on your browser.) You'll notice that you're only allowed to enter numbers, though, which isn't exactly what we want.

Click on "Create Apprenticeship" and you indeed create an apprenticeship, but with useless numbers...

Those numbers are Jedi and Padawan ID's, though none have been created yet. What we need are a bunch of Jedi and Padawan names. Let's put some links in to create some Jedis and Padawans. We need to head over to the views and do some work there. First up, app/views/apprenticeships/index.html.erb. Add the following link code to the bottom of that file:

    <%= link_to 'New Jedi', new_jedi_path %>
    <%= link_to 'New Padawan', new_padawan_path %>

Take a look, reload http://localhost:3000 and we should see:

Ah ha, now we can start making some Jedis and Padawans! First, let's make a bunch of Jedis:

And then, create a slew of Padawans, all eager to become powerful Jedis (go back to http://localhost:3000 to get to the "New Padawan" link):

Now we need to make it easy to pair the Jedis and Padawans up through apprenticeships, so let’s use their names rather than their ID’s through drop down menus. That code is found in the Apprenticeships _form.html.erb partial. Let’s change the end of that file from this:

      <div class="field">
          <%= f.label :jedi_id %><br />
          <%= f.number_field :jedi_id %>
      </div>
      <div class="field">
          <%= f.label :padawan_id %><br />
          <%= f.number_field :padawan_id %>
      </div>
      <div class="actions">
          <%= f.submit %>
      </div>
    <% end %>

to this:

      <div class="field">
        <%= f.select :jedi_id, Jedi.all.collect { |p| [ p.name, p.id ] } %>
      </div>
      <div class="field">
        <%= f.select :padawan_id, Padawan.all.collect { |p| [ p.name, p.id ] } %>
      </div>
      <div class="actions">
        <%= f.submit %>
      </div>
    <% end %>

Now we have drop down menus, populated with all of our Jedis and Padawans, all ready to be synched up through their Apprenticeships!

Create an apprenticeship, and let’s see what happens…

Hmm, we’re almost there, but we’re only seeing the Jedi and Padawan ID’s in the apprenticeship show view. Not very satisfying. Let’s change that so we can see their names.

In views/apprenticeships/show.html.erb, change these lines:

    <p>
      <b>Jedi:</b>
      <%= @apprenticeship.jedi_id %>
    </p>

    <p>
      <b>Padawan:</b>
      <%= @apprenticeship.padawan_id %>
    </p>

to read like this:

    <p>
      <b>Jedi:</b>
      <%= @apprenticeship.jedi.name %>
    </p>

    <p>
      <b>Padawan:</b>
      <%= @apprenticeship.padawan.name %>
    </p>

And reload…

Much better! But, clicking “Back” to get back to the Apprenticeships index, we see those darn ID’s again.

To change that, let’s change the appropriate views/apprenticeships/index.html.erb lines to read from this:

    <% @apprenticeships.each do |apprenticeship| %>
      <tr>
        <td><%= apprenticeship.jedi_id %></td>
        <td><%= apprenticeship.padawan_id %></td>

to this:

    <% @apprenticeships.each do |apprenticeship| %>
      <tr>
        <td><%= apprenticeship.jedi.name %></td>
        <td><%= apprenticeship.padawan.name %></td>

And reloading the apprenticeships index, we see:

That’s what we want to see! Make a few more associations, and we can see all the Apprenticeships we need:

There you go, a step-by-step tutorial on how to make has_many :through associations AND a refresher on Jedi/Padawan apprenticeships in the Star Wars universe. I hope it has helped you avoid some of the confusion I went through when I first was learning this particular association. I know I wasn't the only Code Fellow student making life harder than it needed to be. I learned to let go a little bit and just let Ruby on Rails do some of its magic for us and things went much smoother. So cool.

Find all the code for the “sithfighters” tutorial project on GitHub: https://github.com/mlsayre/sithfighters

Matt Sayre's blog found at: http://gitmatt.com

Shippable CI tips

______

by Matt

A couple of tips for getting Shippable working with your Ruby on Rails Heroku-hosted project.

Shippable is a fast new continuous integration / continuous deployment service that, in my experience, is also one of the easiest to use. I installed it in my latest project a few days ago and thought I'd share a couple of tips. My project is a Ruby on Rails (Rails3) app hosted on Heroku using GitHub revision control. This is probably a similar setup to many, if not most, potential Shippable users, so I hope my experience will be helpful. I only hit a couple of snags that may hang you up as well.

1) PostgreSQL database setup 2) secret_token handling

The first problem I had was the database setup. The first time I ran Shippable, I got this error:

psql: FATAL: Peer authentication failed for user "matt"

Seems "matt" can't access the database. Turns out he doesn't need to. Here is how I got PostgreSQL working with Shippable (with thanks to the TravisCI docs - Shippable shares much in common with Travis).

First, create a new file called database.shippable.yml and place it in your config directory. Copy everything from your database.yml file to the new file, but make sure the username is Postgres and the password is blank (that's the stock username/password for PostgreSQL and all we need for testing). The file should look something like this now:

config/database.shippable.yml:

development:
  adapter: postgresql
  encoding: unicode
  database: appname_development
  pool: 5
  username: postgres
  password:

test:
  adapter: postgresql
  encoding: unicode
  database: appname_test
  pool: 5
  username: postgres
  password:
  min_messages: warning

production:
  adapter: postgresql
  encoding: unicode
  database: appname_production
  pool: 5
  username: yourname
  password:

Great. Now we're ready to make sure Shippable can use that information, so let's open up your shippable.yml file in your root directory. Make sure you have the following line in the 'before_script' section:

shippable.yml:

before_script:
  - cp config/database.shippable.yml config/database.yml

That will copy over the Shippable database file into the original database file. I like having the separate file so I always know where the code came from and why it was needed. Now our database is almost ready to go, but we need to make sure the database is going to be created and ready to run your tests. And once the database is created, we also need to make sure every migration is run, so our shippable.yml file's 'before_script' section will end up looking like this:

shippable.yml:

before_script:
  - cp config/database.shippable.yml config/database.yml
  - psql -c 'create database acroparty_test;' -U postgres
  - rake db:migrate

There we go, the PostgreSQL problem should now be solved.

Now, the second problem I had was figuring out what to do with the secret_token of my project. I had used Figaro to move my secret_token out of the public eye, but that broke things for Shippable. The error I got when running my project through Shippable was:

A secret is required to generate an integrity hash for cookie session data. Use config.secret_token = "some secret phrase of at least 30 characters"in config/initializers/secret_token.rb

Shippable didn't have access to that environment variable, so things came to a screeching halt. I tried for a while to find a way to pass that secret_token to Shippable (Travis has a gem which can hide that variable), but in the end realized it didn't matter what the token was since it was just needed for the tests. So a quick bit of code in the secret_token.rb file fixes this problem:

config/initializers/secret_token.rb:

Yourapp::Application.config.secret_token = if Rails.env.test?
  ('x' * 30)
else
  ENV['SECRET_TOKEN']
end

Now when in the test environment, the secret_token is a bunch of x's. Problem solved!

I hope these two tips can help you get Shippable up and running in your Rails project. The two big strengths for Shippable are its speed (my last run lasted a whopping 39 seconds) and its ease of use. Allowing write access to your project is always a little scary, but in the end I think it saves some headaches and is worth it.

I'll be using Shippable to deploy to Heroku in the coming weeks and if anything comes up there I'll be sure to let you know.


Last posts

___

Shthumbnail

Shippable CI tips

A couple of tips for getting Shippable working with your Ruby on Rails Heroku-hosted project.

Yoda

has_many :through Tutorial

A step-by-step tutorial on how to make a has_many :through association in Ruby on Rails.

Billthecatthumb

Minitest Text-to-speech (pt. 1)

Listen to your tests. Literally! Getting text-to-speech in your MiniTest.


Scroll top