has_many :through with class_name and foreign_key

ruby-on-rails, ruby-on-rails-4

Solution

The issue here is that in

has_many :managers, through: :listing_managers

ActiveRecord can infer that the name of the association on the join model (:listing_managers) because it has the same name as the `has_many :through` association you're defining. That is, both listings and listing_mangers have many managers.

But that's not the case in your other association. There, a listing_manager `has_many :listings`, but a user `has_many :managed_listings`. So ActiveRecord is unable to infer the name of the association on `ListingManager` that it should use.

This is what the `:source` option is for (see http://guides.rubyonrails.org/association_basics.html#has-many-association-reference). So the correct declaration would be:

has_many :managed_listings, through: :listing_managers, source: :listing

(p.s. you don't actually need the `:foreign_key` or `:class_name` options on the other `has_many :through`. You'd use those to define direct associations, and then all you need on a `has_many :through` is to point to the correct association on the `:through` model.)

Problem

I'm working with a fairly straightforward has_many through: situation where I can make the class_name/foreign_key parameters work in one direction but not the other. Perhaps you can help me out. (p.s. I'm using Rails 4 if that makes a diff): English: A User manages many Listings through ListingManager, and a Listing is managed by many Users through ListingManager. Listing manager has some data fields, not germane to this question, so I edited them out in the below code Here's the simple part which works: ``` class User < ActiveRecord::Base has_many :listing_managers has_many :listings, through: :listing_managers end class Listing < ActiveRecord::Base has_many :listing_managers has_many :managers, through: :listing_managers, class_name: "User", foreign_key: "manager_id" end class ListingManager < ActiveRecord::Base belongs_to :listing belongs_to :manager, class_name:"User" attr_accessible :listing_id, :manager_id end ``` as you can guess from above the ListingManager table looks like: ``` create_table "listing_managers", force: true do |t| t.integer "listing_id" t.integer "manager_id" end ``` so the only non-simple here is that ListingManager uses `manager_id` rather than `user_id` Anyway, the above works. I can call `user.listings` to get the Listings associated with the user, and I can call `listing.managers` to get the managers associated with the listing. However (and here's the question), I decided it wasn't terribly meaningful to say `user.listings` since a user can also "own" rather than "manage" listings, what I really wanted was `user.managed_listings` so I tweaked `user.rb` to change has_many :listings, through: :listing_managers to has_many :managed_listings, through: :listing_managers, class_name: "Listing", foreign_key: "listing_id" This is an exact analogy to the code in `listing.rb` above, so I thought this should work right off. Instead my rspec test of this barfs by saying `ActiveRecord::HasManyThroughSourceAssociationNotFoundError: Could not find the source association(s) :managed_listing or :managed_listings in model ListingManager. Try 'has_many :managed_listings, :through => :listing_managers, :source => <name>'. Is it one of :listing or :manager?` the test being: ``` it "manages many managed_listings" do user = FactoryGirl.build(:user) l1 = FactoryGirl.build(:listing) l2 = FactoryGirl.build(:listing) user.managed_listings << l1 user.managed_listings << l2 expect( @user.managed_listings.size ).to eq 2 end ``` Now, I'm convinced I know nothing. Yes, I guess I could do an alias, but I'm bothered that the same technique used in `listing.rb` doesn't seem to work in `user.rb`. Can you help explain? UPDATE: I updated the code to reflect @gregates suggestions, but I'm still running into a problem: I wrote an additional test which fails (and confirmed by "hand"-tesing in the Rails console). When one writes a test like this: ``` it "manages many managed_listings" do l1 = FactoryGirl.create(:listing) @user = User.last ListingManager.destroy_all @before_count = ListingManager.count expect( @before_count ).to eq 0 lm = FactoryGirl.create(:listing_manager, manager_id: @user.id, listing_id: l1.id) expect( @user.managed_listings.count ).to eq 1 end ``` The above fails. Rails generates the error `PG::UndefinedColumn: ERROR: column listing_managers.user_id does not exist` (It should be looking for 'listing_managers.manager_id'). So I think there's still an error on the User side of the association. In `user.rb`'s `has_many :managed_listings, through: :listing_managers, source: :listing`, how does User know to use `manager_id` to get to its Listing(s) ?

Original source