在 rails 中,如何 return 来自另一个 has_many 关联或范围下的两个 has_many 关联的结果

in rails, how to return results from two has_many associations under another has_many association or a scope

我有以下内容:

  has_many :matches_as_mentor, foreign_key: :mentor_id, class_name: 'Match'
  has_many :matches_as_mentee, foreign_key: :mentee_id, class_name: 'Match'

我需要有一个关联 :matches ,它由这两个组合在一起组成。 这样做的好方法是什么?

您有几种不同的方法可以实现这一点。第一个很简单

[*entity.matches_as_mentor, *entity.matches_as_mentee]

第二个有点复杂。您需要使用 UNIONUNION ALL sql 语句取决于您的情况。这是区别 - What is the difference between UNION and UNION ALL?.

您只需要构建一个 SQL 查询来获取这些记录。

第三个是制作include。 Entity.includes(:matches_as_mentor, :matches_as_mentee).

取决于您需要如何处理(显示)它以及您在这些表中有多少记录,您需要选择哪一个。

如果不需要重复,我更喜欢使用 UNION ALL。但你也可以寻找 INNER JOIN.

我想补充的一件事是你可以那样做

Match.where(mentor_id: entity.id).or(Match.where(mentee_id: entity.id)) 

最后一种方法的性能最好。

不幸的是,这并不是 ActiveRecord 关联真的可以很好地处理的情况。关联只是 link 一个模型上的单个外键到另一个模型上的主键,您不能将它们链接在一起。

如果您想加入两者,则需要以下 JOIN 子句:

JOINS matches ON matches.mentor_id = entities.id 
              OR matches.mentee_id = entities.id

你无法得到它,因为关联是 used in so many ways,你不能只是修改连接条件。要创建包含两个类别的单个关联,您需要一个连接 table:

class Entity < ApplicationRecord

  # Easy as pie
  has_many :matchings
  has_many :matches, through: :matchings

  # this is where it gets crazy
  has_many :matchings_as_mentor, 
     class_name: 'Matching',
     ->{ where(matchings: { role: :mentor }) }

  has_many :matches_as_mentor, 
     class_name: 'Match',
     through: :matchings_as_mentor

  has_many :matchings_as_mentee, 
     class_name: 'Matching',
     ->{ where(matchings: { role: :mentee }) }

  has_many :matches_as_mentee, 
     class_name: 'Match',
     through: :matchings_as_mentor
end

class Matching < ApplicationRecord
  enum role: [:mentor, :mentee]
  belongs_to :entity
  belongs_to :match
  validates_uniqueness_of :entity_id, scope: :match_id
  validates_uniqueness_of :match_id, scope: :role
end

class Match < ApplicationRecord
  # Easy as pie
  has_many :matchings
  has_many :entities, through: :matching

  # this is where it gets crazy
  has_one :mentor_matching, 
         class_name: 'Matching',
         ->{ where(matchings: { role: :mentee }) }
  has_one :mentor, through: :mentor_matching, source: :entity

  has_one :mentee_matching, 
         class_name: 'Matching',
         ->{ where(matchings: { role: :mentor }) }
  has_one :mentee, through: :mentor_matching, source: :entity
end

很快您就可以收获同质关联的回报:

entities = Entity.includes(:matches)
entities.each do |e|
  puts e.matches.order(:name).inspect
end 
      

但它要复杂得多,您需要验证和约束以确保一场比赛只能有一个导师和一个受训者。值不值得由你来评价

备选方案是做类似的事情:

Match.where(mentor_id: entity.id)
     .or(Match.where(mentee_id: entity.id)) 

这不能不允许预先加载,因此如果您显示实体列表及其匹配项,它会导致 N+1 查询问题。