在 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]
第二个有点复杂。您需要使用 UNION
或 UNION 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 查询问题。
我有以下内容:
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]
第二个有点复杂。您需要使用 UNION
或 UNION 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 查询问题。