活动记录关联 - 设计问题



概述

我有一个帆船俱乐部示例的一组数据模型,其中有俱乐部的Members,他们与Boat(例如所有者,船长,船员)有关系。 目前正在努力寻找设计 ActiveRecord 关联的最佳方式,以回答有关获取数据的一些问题。 这将包括以下查询:

  • 一个Boat有很多Members
  • 一个Member可以属于一个或多个Boats
  • Member具有一个或多个与Boat关联的角色(船长、船员、所有者)

首次设计尝试

所以我的第一个设计是使用has_many_through关联类型有一个Boat<多对多>Member结构。 联接表将携带与Boat关系类型的属性 这看起来像:

class Boat < ApplicationRecord
has_many :boat_crews
has_many :members, through: :boat_crews
end
class Member < ApplicationRecord
has_many :boat_crews
has_many :boats, through: :boat_crews
end
class BoatCrew < ApplicationRecord
belongs_to :member
belongs_to :boat
end

BoatCrews具有通常的属性id, member_id, boat_id,还包括另一个称为role:string的属性,用于表示与船的关系。 因此,当Member与船只相关联并且条目放置在连接表中以保存与角色的连接信息时。 如果一个Member既是船主又是船长,那么BoatCrews表中有两个角色。

现在,这将启用预期的活动记录关联查询,例如

b = Boat.find(1)
b.members # show all the members associated with the first Boat

唯一的缺点是,如果Member有两个角色(所有者、船长),则b.members查询将返回一个包含重复项的数组(即具有两个角色的成员)。 这可以通过b.members.uniq删除

有没有更好的设计,我以不同的方式建模与船的角色/关系,因此设计尝试第 2 次

第二次设计尝试

再想一想,我想我可以把成员的概念以及与船和一般船员的概念的关系分开。 所以新的模型尝试是

class Member < ApplicationRecord
has_one :crew
end
class Crew < ApplicationRecord
has_many :boat_crews
has_many :boats, through: :boat_crews
belongs_to :member, optional: true
end
class Boat < ApplicationRecord
has_many :boat_crews
has_many :crews, through: :boat_crews
end
class BoatCrew < ApplicationRecord
belongs_to :boat
belongs_to :crew
end

这个想法是Boat有一个Crew集合,反之亦然,Crew模型标识Crew角色的类型(例如船员、船长、所有者)。MemberCrew模型的关联是获取Member信息(例如姓名等)并将其与Boat船员模型的角色相关联。

此设计中的缺陷是,MemberCrew模型之间定义的has-one关系意味着,如果尝试创建member#crew.build类型,则会发生foreign_key错误。

有没有更好的选择

在这一点上,这两种方法似乎都不是正确的。 第一个设计有效,但需要使用额外的约束(需要使用uniq方法)。 第二种设计不起作用,经过反思可能是正确的,因为我定义的语义是"has-one",但我真的不止一个(例如Member有两个角色作为Boat的一部分)。

我想过的一些想法是

  • 恢复为具有联接表的船has_many成员,然后使用该联接表中的新模型对关系类型进行建模
  • 从连接表引入模型是否会引入太多的间接级别?

因此,寻求有关如何解决此问题的任何建议或意见?

除非我误解了什么,否则我认为您的设计尝试 2 无法解决设计尝试 1 出现的"问题"。如果一个成员只能有一个"船员",那么当他们被视为船员的一部分时,他们仍然会被加倍计算,现在你有一个重复的船员(指向同一个成员)。

虽然这不是一个完美的规则,但在进行初始架构时,尽可能接近现实世界地建模是一个不错的经验法则。优化可以稍后进行。在这种情况下,我会尝试 1,但不要使用uniq(因为这需要将所有内容加载到 ruby 中,然后获得不同的记录,这很慢)。改用 rails 活动记录查询接口。你可以使用类似的东西,

b.members.distinct

它会生成一个SELECT DISTINCT ...查询,用于何时需要唯一成员。但实际上,您很可能想要查询成员及其角色的知识。我不确定在纯 rails 活动查询接口中执行此操作的最简单方法,但是,如果您想获取成员及其所有角色,您可以随时使用 array_agg。

例如

SELECT members.name, array_agg(boat_crews.role) AS roles FROM members
INNER JOIN boat_crews on members.id = boat_crews.member_id
INNER JOIN boats on boat_crews.boat_id = boats.id
WHERE boats.id = 1
GROUP BY members.name

会返回类似的东西


names | roles
joe   | ["rower"]
john  | ["skipper","captain"]

将获取所有成员的名称,以及他们各自的所有角色作为一个数组。我留给您将其转换为活动记录查询。

一种相当冗长的说法,选择选项 1,并在需要某些查询的帮助时在此处提出具体问题。

最新更新