Rails 迁移:同一个类之间的多对多关系 |人际关系
Posted
技术标签:
【中文标题】Rails 迁移:同一个类之间的多对多关系 |人际关系【英文标题】:Rails migrations: many-to-many relationship between the same class | Interpersonal Relationships 【发布时间】:2021-10-29 10:06:34 【问题描述】:我想在关系的帮助下连接两个人。
Person:
* id
* name
Relationship:
* person_a_id
* person_b_id
* connection # an enum with values like "colleagues", "friends", "siblings", etc
我想完成几件事:
我创建了 2 个人。我希望在查询Person.first.people
时包含第二个人反之亦然! (即当我查询Person.second.people
时应包括第一人)。我很接近通过has_and_belongs_to_many :(people|reversed_people)
:https://***.com/a/46230787/6030239 实现这一目标
连接两者的关系的连接值为friends
。我想创建一个has_many :friends
方法,这样第二个人就会出现在 Person.first.friends 查询中反之亦然!
【问题讨论】:
【参考方案1】:首先 - 我不会在这里使用 HABTM,而是使用自己的模型命名连接表(称为关系)并使用 has_many :people, through: :relationships
。
我创建了 2 个人。我希望在查询 Person.first.people 时包含第二个人,反之亦然! (即当我查询 Person.second.people 时,应包括第一个人)。我已经接近通过 has_and_belongs_to_many 实现这一目标:(people|reversed_people): https://***.com/a/46230787/6030239
您可以通过为每个关系添加两行(每个方向一个)来使用单个活动记录关系(has-many through 或 HABTM)来实现此目的。例如:
def add_bi_directional_relationship(first:, second:, type:)
Relation.create!(person_a: first, person_b: second, connection: type)
Relation.create!(person_a: second, person_b: first, connection: type)
end
# first.people => [second]
# second.people => [first]
Activerecord 关联旨在通过外键查询表,因此要以简单的方式使用它们,您需要一个表,其中要查询的值将位于单个列中。
但是,为什么需要通过 activerecord 关联来完成呢?您可以编写一个方法来执行您需要的查询。
class Person
has_many :forward_people, through: :relations, #...
has_many :reversed_people, through: :relations, #...
def people
forward_people + reversed_people
# or build the joins in SQL strings or Arel
end
end
或许多其他潜在的解决方案,具体取决于您的需求。
将两者联系起来的关系具有朋友的联系价值。我想创建一个 has_many :friends 方法,这样第二个人就会出现在 Person.first.friends 查询中,反之亦然!
不确定我是否完全理解这一点...但是如果您采用第一种方法为每个方向添加关系,那么您可以编写如下方法:
def friends
people.where(relations: connection: 'friends')
end
您也可以使用另一个 activerecord 关系来执行此操作...但我不建议为相同的外键定义多个 activerecord 关联。另一种方法是定义一个.friends
范围,允许您执行Person.first.people.friends
。
一般来说,这些目标很难实现,因为您正在指定一个实现(返回特定值的 activerecord 关联),而没有说明您为什么需要它来完成它/您试图用它解决什么问题。 Activerecord 关联在一定程度上很有帮助,但它们也引入了一个抽象层,可能会增加复杂性/混乱。讨论您想要解决的实际业务/应用程序需求(例如,显示 X 的报告、呈现 X 的视图或保存 X 的表单)将允许人们提出可能更适合您正在尝试的替代方法完成。
【讨论】:
【参考方案2】:由于您无法在 ActiveRecord 中定义关联,其中外键是两列之一,您可以通过添加额外的连接表来进行设置:
class Person
has_many :personal_relationships
has_many :relationships, through: :personal_relationships
has_many :related_people, through: :relationships,
source: :persons
end
class Relationship
has_many :personal_relationships
has_many :persons, through: :personal_relationships
end
class PersonalRelationship
belongs_to :person
belongs_to :relationship
validates_uniqueness_of :person_id, scope: :relationship_id
end
has_and_belongs_to_many
在这里不相关。它真的只处理最微不足道的情况,即使有最隐晦的复杂性,它也会一脸茫然。
您将通过以下方式在两个用户之间创建关系:
@relationship = Relationship.new
@relationship.persons << user_1
@relationship.persons << user_2
这会在personal_relationships
表中创建两行。
虽然这看起来有点不靠谱,但它为您提供了一个单一的关联,可以正确地预先加载并将其视为同质集合。缺点是您需要更多的数据库行,并且如果您想确保关系只能与数据库级别的人建立关系,则需要使用一些更高级的技术,例如自定义约束。
【讨论】:
你好,马克斯!感谢您分享您的想法。我喜欢你的方法,但是验证似乎不起作用。我能够在同一个人之间建立多种关系,这不是预期的结果。您能否描述一下您建议的验证是做什么的?我怎样才能防止在同一个人之间建立多个联系? 我在关系模型中添加了一个简单的连接枚举,并为 Person 创建了friends
方法。好像是驴。您能否在答案中提出改进建议? def friends people_ids = [] relationships.friends.each do |r| r.personal_relationships.where.not(person_id: id).each do |pr| people_ids << pr.person_id end end Person.where(id: people_ids) end
以上是关于Rails 迁移:同一个类之间的多对多关系 |人际关系的主要内容,如果未能解决你的问题,请参考以下文章
如何关联数据库中通过 Rails 中的多对多关系连接的两个条目?
如何在 Ruby on Rails 中管理嵌套字段的多对多关系