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.
A simple test management system -
From the diagram, 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
end
class Case < ActiveRecord::Base
has_many :releases, :through=>runs
has_many :runs
end
class Runs < ActiveRecord::Base
belongs_to :case
belongs_to :release
end
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
end
create_table :cases do |t|
t.string :title
t.string :steps
t.string :expected
end
create_table :runs do |t|
t.integer :release_id
t.integer :case_id
t.string :result
end
end
def self.down
drop_table :releases
drop_table :cases
drop_table :runs
end
end
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')
I like Spritecloud!
ReplyDelete