Ruby-on-rails has_many :通过 class_name 和 foreign_key

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/18154736/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-02 22:30:44  来源:igfitidea点击:

has_many :through with class_name and foreign_key

ruby-on-railsruby-on-rails-4

提问by JCQ

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):

我正在使用一个相当简单的 has_many through: 的情况,我可以让 class_name/foreign_key 参数在一个方向上工作,而不是另一个方向。或许你可以帮帮我。(ps,如果有差异,我将使用 Rails 4):

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

中文:一个User通过ListingManager管理多个Listing,一个Listing通过ListingManager被多个User管理。列表管理器有一些数据字段,与这个问题无关,所以我在下面的代码中将它们编辑了出来

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:

正如您可以从上面猜到的 ListingManager 表看起来像:

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_idrather than user_id

所以这里唯一不简单的是 ListingManager 使用manager_id而不是user_id

Anyway, the above works. I can call user.listingsto get the Listings associated with the user, and I can call listing.managersto get the managers associated with the listing.

无论如何,上述工作。我可以打电话user.listings获取与用户关联的列表,我可以调用listing.managers获取与列表关联的经理。

However (and here's the question), I decided it wasn't terribly meaningful to say user.listingssince a user can also "own" rather than "manage" listings, what I really wanted was user.managed_listingsso I tweaked user.rbto change has_many :listings, through: :listing_managers to has_many :managed_listings, through: :listing_managers, class_name: "Listing", foreign_key: "listing_id"

然而(这是问题所在),我认为这不是很有意义,user.listings因为用户也可以“拥有”而不是“管理”列表,我真正想要的是user.managed_listings所以我调整user.rb以更改 has_many :listings,通过: : list_managers 到 has_many :managed_listings,通过::listing_managers, class_name: "Listing", foreign_key: "listing_id"

This is an exact analogy to the code in listing.rbabove, 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?

这与listing.rb上面的代码完全相似,所以我认为这应该可以正常工作。相反,我说这个 barfs 的 rspec 测试 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.rbdoesn't seem to work in user.rb. Can you help explain?

现在,我确信我什么都不知道。是的,我想我可以做一个别名,但我困扰,在使用相同的技术listing.rb似乎并没有工作user.rb。你能帮忙解释一下吗?

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:

更新:我更新了代码以反映@gregates 的建议,但我仍然遇到一个问题:我编写了一个失败的附加测试(并通过 Rails 控制台中的“手动”测试确认)。当一个人写这样的测试时:

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_idto get to its Listing(s) ?

以上失败。Rails 生成错误PG::UndefinedColumn: ERROR: column listing_managers.user_id does not exist(它应该寻找'listing_managers.manager_id')。所以我认为关联的用户端仍然存在错误。在user.rb's 中has_many :managed_listings, through: :listing_managers, source: :listing,用户如何知道使用它manager_id来访问其列表?

回答by gregates

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 nameas the has_many :throughassociation you're defining. That is, both listings and listing_mangers have many managers.

ActiveRecord 可以推断出连接模型 (:listing_managers) 上关联的名称,因为它has_many :through与您定义的关联具有相同的名称。也就是说,listings 和 listing_mangers 都有很多manager

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 ListingManagerthat it should use.

但在您的其他协会中,情况并非如此。在那里,一个 listing_manager has_many :listings,但一个 user has_many :managed_listings。因此 ActiveRecord 无法推断出ListingManager它应该使用的关联名称。

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

这就是该:source选项的用途(请参阅http://guides.rubyonrails.org/association_basics.html#has-many-association-reference)。所以正确的声明是:

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

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

(p.s. you don't actually need the :foreign_keyor :class_nameoptions on the other has_many :through. You'd use those to define directassociations, and then all you need on a has_many :throughis to point to the correct association on the :throughmodel.)

(ps,您实际上并不需要其他的:foreign_keyor:class_name选项has_many :through。您可以使用它们来定义直接关联,然后您需要在 ahas_many :through上指向:through模型上的正确关联。)

回答by Nic

I know this is an old question, but I just spent some time running into the same errors and finally figured it out. This is what I did:

我知道这是一个老问题,但我只是花了一些时间遇到了同样的错误,最后才弄明白。这就是我所做的:

class User < ActiveRecord::Base
  has_many :listing_managers
  has_many :managed_listings, through: :listing_managers, source: :listing
end

class Listing < ActiveRecord::Base
  has_many :listing_managers
  has_many :managers, through: :listing_managers, source: :user
end

class ListingManager < ActiveRecord::Base
  belongs_to :listing
  belongs_to :user
end

This is what the ListingManager join table looks like:

这是 ListingManager 连接表的样子:

create_table :listing_managers do |t|
  t.integer :listing_id
  t.integer :user_id
end

Hope this helps future searchers.

希望这有助于未来的搜索者。

回答by d1jhoni1b

i had several issues with my models, had to add the foreign key as well as the source and class name... this was the only workaround i found:

我的模型有几个问题,必须添加外键以及源和类名...这是我发现的唯一解决方法:

  has_many :ticket_purchase_details, foreign_key: :ticket_purchase_id, source: :ticket_purchase_details, class_name: 'TicketPurchaseDetail'

  has_many :details, through: :ticket_purchase_details, source: :ticket_purchase, class_name: 'TicketPurchaseDetail'