creating factories with many-to-many associations w/ join table (has_many :through join table)

1,276 views
Skip to first unread message

Dave

unread,
May 22, 2009, 11:17:24 AM5/22/09
to factory_girl
Hi,

I'm having trouble creating many-to-many associations, using a join
table, correctly in factory_girl. Here are the models and factories

<code>
class Album < ActiveRecord::Base
has_many :features # join
table
has_many :albums, :through => :features # many-to-many
association
end

class Artist < ActiveRecord::Base
has_many :features # join
table
has_many :albums, :through => :features # many-to-many
association
end

class Feature < ActiveRecord::Base
belongs_to :album
belongs_to :artist
end

Factory.define(:album) { |album|
album.name "FooTime"
album.genre "FooRock"
album.description "A good time"
album.association :feature
#album.association :artist # results in stack level too deep
}

Factory.define(:artist) { |artist|
artist.name "FooFighters"
artist.association :feature
#artist.association :album # results in stack level too deep
}

Factory.define(:feature) { |feature|

feature.artist_id {|o|[o.association(:artist)]}
feature.album_id {|o| [o.association(:album)]}
}
</code>

I'm using cucumber, so I create the Factories in ./features/support/
env.rb in a 'before do' block:

<code>
Before do
#killing the table data as because we aren't using fixtures
Album.destroy_all
Artist.destroy_all
Feature.destroy_all
#recreating the data
album = Factory(:album)
artist = Factory(:artist)
feature = Factory(:feature)
end
</code>

Any ideas? Should I be using build and then saving the built instances
using save or save! (e.g. artist.save, feature.save) or create? How
does factory_girl create the associations? Should I be creating the
associated factories inside the associated Factory definitions
themselves? Just trying to get my head around it, thanks for the help
ahead of time.

Thanks,
Dave

Josh Clayton

unread,
May 22, 2009, 11:44:23 AM5/22/09
to factory_girl
Dave,

First things first, could you verify the association in on Album is

class Album < ActiveRecord::Base
has_many :features
has_many :artists, :through => :features # you listed this as
has_many :albums, which is incorrect
end

Please respond once that's cleared up; I'm guessing that's why you're
getting a stack level too deep error, since album is referencing
itself.

Dave

unread,
May 22, 2009, 12:15:10 PM5/22/09
to factory_girl
wow, good catch, I fixed the has_many :artists in the Album model,
though regretfully the factories' associations are still broken. Any
ideas?
Thanks, Dave

Dave

unread,
May 22, 2009, 12:28:58 PM5/22/09
to factory_girl
I should specifically mention, I still get the stack level too deep
error after fixing the has_many bug above:

$> cucumber features/test_associations.feature

Feature: Testing associations
As a rails nut
I want to test factory girl associations

Scenario: factory girl associations tested # features/
test_associations.feature:5
stack level too deep (SystemStackError)
~/music_industry/spec/factories/feature_factory.rb:2
~/music_industry/spec/factories/feature_factory.rb:2
...
~/music_industry/spec/factories/feature_factory.rb:2
~/music_industry/spec/factories/feature_factory.rb:2
./features/support/env.rb:34:in `Before'

Thanks,
Dave

Josh Clayton

unread,
May 22, 2009, 1:19:35 PM5/22/09
to factory_girl
Dave,

It looks like the error's coming from your feature_factory.rb file.

FactoryGirl's association conventions allow us to define a factory
with the associations like such:

Factory.define(:feature) {|feature|
feature.association :artist
feature.association :album
}

As for the Album and Artist factories, you'll want to remove the
association for :artist and :album, respectively (you already
commented them out - you'll want to remove them completely).

Let me know where that puts you. Thanks!

Dave

unread,
May 22, 2009, 2:39:05 PM5/22/09
to factory_girl
Thanks Josh! It works! (mostly) I was able to create a many to many
association with the two parents and the join table, though it looks
like the Album and Artist factories are created twice each, which is
my next task, though it probably won't impact testing... You
definitely pointed me in the right direction, creating the factory
associations from the join table to the parents rather than from the
parents to the join table. I've added some comments to help others
with similar issues.

<script-console-output>
$> RAILS_ENV=test script/console
Loading test environment (Rails 2.3.2)
>> @artists = Artist.find(:all)
=> [#<Artist id: 8, name: "FooFighters", created_at: "2009-05-22
17:53:01", updated_at: "2009-05-22 17:53:01">,
#<Artist id: 9, name: "FooFighters", created_at: "2009-05-22
17:53:01", updated_at: "2009-05-22 17:53:01">]

>> @albums = Album.find(:all)
=> [#<Album id: 6, genre: "FooRock", name: "FooTime", description: "A
good time", created_at: "2009-05-22 17:53:01", updated_at: "2009-05-22
17:53:01">,
#<Album id: 7, genre: "FooRock", name: "FooTime", description: "A good
time", created_at: "2009-05-22 17:53:01", updated_at: "2009-05-22
17:53:01">]
>> @features = Feature.find(:all)
=> [#<Feature id: 7, artist_id: 8, album_id: 6, number_of_songs: nil,
created_at: "2009-05-22 17:53:01", updated_at: "2009-05-22 17:53:01">]
>
</script-console-output>

Here is a dump of the sqlite3 tables using 'RAILS_ENV=test script/
dbconsole'

<sqlite3-output>
CREATE TABLE "artists" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT
NULL, "name" varchar(255), "created_at" datetime, "updated_at"
datetime);
INSERT INTO "artists" VALUES(8,'FooFighters','2009-05-22
17:53:01','2009-05-22 17:53:01');
INSERT INTO "artists" VALUES(9,'FooFighters','2009-05-22
17:53:01','2009-05-22 17:53:01');

CREATE TABLE "albums" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT
NULL, "genre" varchar(255), "name" varchar(255), "description" varchar
(255), "created_at" datetime, "updated_at" datetime);
INSERT INTO "albums" VALUES(6,'FooRock','FooTime','A good
time','2009-05-22 17:53:01','2009-05-22 17:53:01');
INSERT INTO "albums" VALUES(7,'FooRock','FooTime','A good
time','2009-05-22 17:53:01','2009-05-22 17:53:01');

CREATE TABLE "features" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT
NULL, "artist_id" integer, "album_id" integer, "number_of_songs"
integer, "created_at" datetime, "updated_at" datetime);
INSERT INTO "features" VALUES(7,8,6,NULL,'2009-05-22
17:53:01','2009-05-22 17:53:01');
</sqlite3-output>

Here is the revised code
<code>
# models
class Artist < ActiveRecord::Base
has_many :features
has_many :albums, :through => :features
end

class Album < ActiveRecord::Base
has_many :features
has_many :artists, :through => :features
end

class Feature < ActiveRecord::Base
belongs_to :album
belongs_to :artist
end

# revised factories
# created the associations from the join table only
Factory.define(:artist) do |artist|
artist.name "FooFighters"
end

Factory.define(:album) do |album|
album.name "FooTime"
album.genre "FooRock"
album.description "A good time"
end

# join table factory - :feature
Factory.define(:feature) do |feature|
feature.association :artist
feature.association :album
end

# features/support/env.rb # cucumber environment file used to setup
factories
Before do
#killing the table data as we aren't using fixtures
Album.destroy_all
Artist.destroy_all
Feature.destroy_all

#recreating the data
# **** Here's the magic ****
# building the parent factories first, and saving them
# after the join table record is fully created in db
album = Factory.build(:album)
artist = Factory.build(:artist)
# using create here as the join table is written to
# the db first with id's from album and artist factories.
# you could use 'feature = Factory(:feature)'
# though using 'create' method for clarity.
feature = Factory.create(:feature)
album.save
artist.save
end
</code>

Thanks again,
Dave
Reply all
Reply to author
Forward
0 new messages