BTW, I borrowed this particular application from http://www.jumbabox.com/2008/06/ruby-on-rails-many-to-many-tutorial/ if you need more explanation on the models used in this example.
On to the solution:
Here are the models:
class Album < ActiveRecord::Base
has_many :features
has_many :artists, :through => :features
end
class Artist < ActiveRecord::Base
has_many :features
has_many :albums, :through => :features
end
class Feature < ActiveRecord::Base
belongs_to :album
belongs_to :artist
end
As you can see, the Artist and Album models are the parents and the Feature model is the join table. This produces the desired many-to-many association with 'has_many /(:artists|:albums)/, :through => :features'.
Here are the factories... There are two big concepts that should be mentioned that allow this association to be created properly. For one, the associations in the factories should be created from the join table back to the parents. This enables the parents' records to be created at the same time that the join table is created with the link between the three intact.
The second big concept is that wherever you create your factories (in my case, I'm using cucumber and create the factories in './features/support/env.rb') the parents factories are only @instantiated and returned, though not written to the database. This is done using the 'Factory.build(:factory_name)' method rather than the Factory(:factory_name) or Factory.create(:factory_name) methods. If you used create you would save multiple records to the database with no associations to the join table. When the join table factory is 'create'd, the associations that were defined in the join table factory to the parents create the associated parent's records for you.# revised factories
# created the associations from the join table only
Factory.define(:artist) do |artist|
artist.name "FooFighters"
endFactory.define(:album) do |album|
# join table factory - :feature
album.name "FooTime"
album.genre "FooRock"
album.description "A good time"
end
Factory.define(:feature) do |feature|
feature.association :artist
feature.association :album
end
Here is how I create the factories in ./features/support/env.rb (NOTE: these factories are created in a Before block that kills all data in the database tables because we want to reset the database as we aren't using transactional fixtures, then reconstructs them with the factories):
This produces the following output from 'RAILS_ENV=test script/console':
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
# through the join table so that the
# 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)
end
Great Success!Loading test environment (Rails 2.3.2)
>> @feature = Feature.find(:all)
=> [#Feature id: 8, artist_id: 10, album_id: 8, number_of_songs: nil,
created_at: "2009-05-23 15:03:25", updated_at: "2009-05-23 15:03:25"]
>> @artist = Artist.find(:all)
=> [#Artist id: 10, name: "FooFighters", created_at: "2009-05-23 15:03:25",
updated_at: "2009-05-23 15:03:25"]
>> @album = Album.find(:all)
=> [#Album id: 8, genre: "FooRock", name: "FooTime", description: "A good time",
created_at: "2009-05-23 15:03:25", updated_at: "2009-05-23 15:03:25"]
>>
I hope that was clear... hit me up if you are still having trouble with this.