Convert has_and_belongs_to_many to a has_many :through association
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.
