So there are plenty of resources out there to learn how to use has_many :through associations.
I followed them over and over again but couldn’t get my code to work. I knew I had the basic structure setup correctly, since the examples are pretty straightforward, and the concept is not difficult. My has_and_belongs_to_many code originally looked like this:
class Soda < ActiveRecord::Base
has_and_belongs_to_many :distributors
end
class Distributor < ActiveRecord::Base
has_and_belongs_to_many :sodas
end
Of course there was also a many-to-many join table migration:
class DistributorsSodasJoinTable < ActiveRecord::Migration
def self.up
create_table :distributors_sodas, :id => false do |t|
t.column :soda_id, :int
t.column :distributor_id, :int
end
end
def self.down
drop_table :distributors_sodas
end
end
This works quite nicely:
>> Soda.find(1).distributors
=> []
Later found that I needed to add attributes in the join table to associate extra fields on the Distributors <-> Sodas relationship. has_and_belongs_to_many does not have a Rails way to access those extra fields in the join table. I’ve successfully done it through SQL, but much guilt and remorse lead me to finally learn has_many :through.
This was my best initial attempt:
class Soda < ActiveRecord::Base
has_many :distributors_sodas
has_many :distributors, :through =>; :distributors_sodas
end
class Distributor < ActiveRecord::Base
has_many :distributors_sodas
has_many :distributors, :through => :distributors_sodas
end
class DistributorsSodas < ActiveRecord::Base
belongs_to :soda
belongs_to :distributor
end
All goes well until I try to do a quick test:
>> Soda.find(1).distributors
NameError: uninitialized constant Soda::DistributorsSoda
...
from (irb):4
Umm… what? I never tried to instantiate an object of the type Soda::DistributorsSoda. Instead, I was simply trying to use the DistributorsSodas ActiveRecord object, right?
It turns out that has_many :through (apparently) can’t handle using the join tables created by has_and_belongs_to_many. Its just a naming issue - has_many :through will work fine using a one-to-many join table like distributor_sodas (note the missing ’s’ on distributor). If you need a many-to-many join, you have to rename the table to fix the (pluralization?) problem. I deleted the DistributorsSodas model and created the Store model.
class Soda < ActiveRecord::Base
has_many :stores
has_many :distributors, :through => :stores
end
class Distributor < ActiveRecord::Base
has_many :stores
has_many :sodas, :through => :stores
end
class Store < ActiveRecord::Base
belongs_to :soda
belongs_to :distributor
end
This proved a much better result:
>> Soda.find(1).distributors
=> []
In the end, the association naming convention actually make more sense. I was bummed to have to change the table/model names though.
Please comment if you know how to create the association without changing the model name.