Let's talk about testing

activerecord has_many :through relationship

There are a good number of different join models with Rails, some more complicated than others.
One of my favorite is has_many :through, it lets you join two models together using a separate join table which can also hold extra attributes for that join.

So… a release has many test cases, but a test case has many releases. A simple HABTM join would almost do here, except, when a test case is part of a release it should have a result (PASS, FAIL, NOT_RUN etc). So a join table is needed to store these extra attributes.
We also want a test case to have the result of NOT_RUN when its add to a release.

So diving into the models while keeping an eye on the diagram above

class Release < ActiveRecord::Base
has_many :cases, :through=>runs
has_many :runs

class Case < ActiveRecord::Base
has_many :releases, :through=>runs
has_many :runs

class Runs < ActiveRecord::Base
belongs_to :case
belongs_to :release

Now for the migrations.
The interesting one is the ‘runs’ table. It contains an identifier to Release and another
one to Case, as well as a third column to store the result. The Release and Case objects dont need any extra identifiers, as its all managed in the Run table.

class SetItUp < ActiveRecord::Migration
def self.up
create_table :releases do |t|
t.string :version

create_table :cases do |t|
t.string :title
t.string :steps
t.string :expected

create_table :runs do |t|
t.integer :release_id
t.integer :case_id
t.string :result

def self.down
drop_table :releases
drop_table :cases
drop_table :runs

Next on the agenda – using them in the console. First lets create a release and two test cases, c1 and c2.

>> r = Release.create(:version=>1)
>> c1 = Case.create(:title=>’A Title’, :steps=>’A step’, :expected=>’Expected Stuff’)
>> c2 = Case.create(:title=>’Another Title’, :steps=>’Another step’, :expected=>’Expected Stuff’)

There are two ways to add a case to a release now. First lets use the << operator, which will automatically create the association between the release and the case.

>> r.cases << c1

Now if you look at the runs table in mysql, you will see release_id and case_id are both filled but the result is empty. Not exactly what we’re after.

| id | release_id | case_id | result | created_at | updated_at |
| 1 | 1 | 1 | NULL | 2009-08-16 15:51:34 | 2009-08-16 15:51:34 |

The next option is to explicitly set the release, the case and the result in the join.

>> Run.create(:release => r, :case => c, :result=>’NOT_RUN’)

Now if we take a look, we can see all the fields we want are filled! Great, now we can go on with testing our release.

| id | release_id | case_id | result | created_at | updated_at |
| 2 | 1 | 2 | NOT_RUN| 2009-08-16 15:53:15 | 2009-08-16 15:53:15 |

However, when we want to change the result in the Run table, we will have to reference the Run object directly.

>> Run.find(1).update_attributes(:result=>’PASS’)

One response to “activerecord has_many :through relationship”

  1. Steve says:

    I have a similar case but I can’t get the extra attribute in the join table to update with devise and simple_form. Tried using accept_nested_attributes but it adds an new record along with the has_many through records. Thanks.

Reputation. Meet spriteCloud

Find out today why startups, SMBs, enterprises, brands, digital agencies, e-commerce, and mobile clients turn to spriteCloud to help improve their customer experiences. And their reputation. With complete range of QA services, we provide a full service that includes test planning, functional testing, test automation, performance testing, consultancy, mobile testing, and security testing. We even have a test lab — open to all our clients to use — with a full range of devices and platforms.

Discover how our process can boost your reputation.

We use cookies to give you the best browsing experience possible. For more information, please read our cookie policy.

The cookie settings on this website are set to "allow cookies" to give you the best browsing experience possible. If you continue to use this website without changing your cookie settings or you click "Accept" below then you are consenting to this.