Overview
Mongoid 中的关联允许您在模型之间创建关系。在本指南中,您可以学习;了解如何使用 Mongoid 来自定义关联在应用程序中的行为方式。以下部分描述了自定义关联行为的方法。
扩展
扩展允许您向关联添加自定义功能。 您可以通过在关联定义中指定区块来定义关联的扩展,如以下示例所示:
class Band include Mongoid::Document embeds_many :albums do def find_by_name(name) where(name: name).first end end end band.albums.find_by_name("Omega") # returns album "Omega"
自定义关联名称
您可以使用 class_name 宏为关联指定自定义类名称。当您想要将关联命名为类名以外的名称时,这非常有用。以下示例使用 class_name 宏指定名为 records 的嵌入式关联表示 Album 类:
class Band include Mongoid::Document embeds_many :records, class_name: "Album" end
自定义键
默认下,Mongoid 在查找关联时使用父类的 _id字段。 您可以使用 primary_key 和 foreign_key 宏来指定要使用的不同字段。 以下示例为 Band 类上的 albums 关联指定新的主节点 (primary node in the replica set)和外键:
class Band include Mongoid::Document field :band_id, type: String has_many :albums, primary_key: 'band_id', foreign_key: 'band_id_ref' end class Album include Mongoid::Document field :band_id_ref, type: String belongs_to :band, primary_key: 'band_id', foreign_key: 'band_id_ref' end
如果要指定 has_and_belongs_to_many 关联,则还可以使用 inverse_primary_key 和 inverse_foreign_key 宏。 inverse_primary_key 宏指定本地模型上远程模型用于查找文档的字段。 inverse_foreign_key 宏指定远程模型上用于存储在 inverse_primary_key 中找到的值的字段。
以下示例为 has_and_belongs_to_many 关联中的 Band 和 Members 类指定了新的主节点 (primary node in the replica set)键和外键:
class Band include Mongoid::Document field :band_id, type: String field :member_ids, type: Array has_many :members, primary_key: 'member_id', foreign_key: 'member_ids', inverse_primary_key: 'band_id', inverse_foreign_key: 'band_ids' end class Member include Mongoid::Document field :member_id, type: String field :band_ids, type: Array has_many :bands, primary_key: 'band_id', foreign_key: 'band_ids', inverse_primary_key: 'member_id', inverse_foreign_key: 'member_ids' end
自定义范围
您可以使用 scope 参数指定关联的范围。 scope 参数确定 Mongoid 将哪些文档视为关联的一部分。 作用域关联在查询时仅返回与作用域条件匹配的文档。 您可以将 scope设立为元数为零的 Proc 或引用关联模型上命名范围的 Symbol。 以下示例在 Band 类中的关联上设置自定义作用域:
class Band include Mongoid::Document has_many :albums, scope: -> { where(published: true) } # Uses a scope called "upcoming" on the Tour model has_many :tours, scope: :upcoming end
注意
您可以将与范围条件不匹配的文档添加到关联中。 Mongoid 将文档保存到数据库,并且它们将显示在关联的内存中。 但是,在查询关联时,您将看不到这些文档。
验证
当 Mongoid 将关联加载到内存时,默认下,它使用 validates_associated 宏来验证是否存在任何子项。 Mongoid 验证子项的以下关联类型:
embeds_manyembeds_onehas_manyhas_onehas_and_belongs_to_many
您可以在定义关联时将 validate 宏设置为 false,以关闭此验证行为,如以下示例所示:
class Band include Mongoid::Document embeds_many :albums, validate: false end
多态性
Mongoid 支持一对一和一对多关联的子类的多态性。 多态关联允许单个关联包含不同类类型的对象。 您可以通过在子关联中将 polymorphic 选项设置为 true 并将 as 选项添加到父关联中来定义多态关联。 以下示例在 Band 类中创建多态关联:
class Tour include Mongoid::Document has_one :band, as: :featured end class Label include Mongoid::Document has_one :band, as: :featured end class Band include Mongoid::Document belongs_to :featured, polymorphic: true end
在前面的示例中,Band 类中的 :featured 关联可以包含 Label 或 Album文档。
重要
Mongoid 仅支持从子节点到父节点的多态性。 您不能将父 has_one 或 has_many 关联指定为多态。
has_and_belongs_to_many 关联不支持多态关联。
自定义多态类型
从版本 9.0.2 开始, Mongoid 通过全局注册表支持自定义多态类型。 您可以指定备用键来表示不同的类,从而将代码与数据解耦。 以下示例将字符串 "artist" 指定为 Band 类的备用键:
class Band include Mongoid::Document identify_as 'artist' has_many :albums, as: :record end
在前面的示例中,identify_as 指令指示 Mongoid 将 Band 类作为字符串 "artist"存储在数据库中。
您还可以指定多个别名,如以下示例所示:
class Band include Mongoid::Document identify_as 'artist', 'group', 'troupe' has_many :albums, as: :record end
在前面的示例中,artist 是默认名称,其他名称仅用于查找记录。 这样,您就可以在不破坏数据关联的情况下重构代码。
多态类型别名是全局性的。 您指定的键在整个代码库中必须是唯一的。 但是,您可以注册可用于不同模型子集的替代解析程序。 在这种情况下,每个解析程序的键必须是唯一的。 以下示例展示了如何注册备用解析程序:
Mongoid::ModelResolver.register_resolver Mongoid::ModelResolver.new, :mus Mongoid::ModelResolver.register_resolver Mongoid::ModelResolver.new, :tool module Music class Band include Mongoid::Document identify_as 'bnd', resolver: :mus end end module Tools class Band include Mongoid::Document identify_as 'bnd', resolver: :tool end end
Music::Band 和 Tools::Band 的别名都为 "bnd",但每个模型都使用自己的解析程序来避免冲突。
相关行为
您可以为引用的关联提供 dependent 选项,以指定删除文档时 Mongoid 如何处理关联的文档。 您可以指定以下选项:
delete_all:删除所有子文档而不运行任何模型回调。destroy:删除子文档并运行所有模型回调。nullify:将子文档的外键设置为nil。 如果子文档仅被父文档引用,则该子文档可能会成为孤立文档。restrict_with_exception:如果子文档不为空,则引发异常。restrict_with_error:如果子文档不为空,则取消操作并返回false。
如果您不指定任何 dependent 选项,则 Mongoid 在删除父文档时将保持子文档不变。 子文档会继续引用已删除的父文档,如果仅通过父文档引用,则子文档将成为孤立文档。
以下示例在 Band 类上指定了 dependent 选项:
class Band include Mongoid::Document has_many :albums, dependent: :delete_all belongs_to :label, dependent: :nullify end
自动保存引用的关联
默认下,Mongoid 在保存父文档时不会自动保存非嵌入式关联中的关联文档。 这可能会导致对不存在的文档进行悬空引用。
您可以对引用的关联使用 autosave 选项,以便在保存父文档时自动保存关联的文档。 以下示例创建了一个具有关联 Album 类的 Band 类,并指定了 autosave 选项:
class Band include Mongoid::Document has_many :albums end class Album include Mongoid::Document belongs_to :band, autosave: true end band = Band.new album = Album.create!(band: band) # The band is persisted at this point.
注意
Mongoid 会自动将自动保存功能添加到使用 accepts_nested_attributes_for 选项的关联中。
不需要为嵌入式关联指定 autosave 选项,因为 Mongoid 会将嵌入式文档保存在父文档中。
自动构建
您可以将 autobuild 选项添加到一对一关联,例如 has_one 和 embeds_one,以便在访问 nil 关联时自动实例化新文档。 以下示例将 autobuild 选项添加到 Band 类的关联中:
class Band include Mongoid::Document embeds_one :label, autobuild: true has_one :producer, autobuild: true end
触摸
当 Mongoid 接触文档时,它会将文档的updated_at 字段更新为当前日期和时间。您可以将 touch 选项添加到任何 belongs_to 关联中,以确保 Mongoid 在每次子文档更新时都会接触到父文档。 以下示例将 touch 选项添加到 Band 类的关联中:
class Band include Mongoid::Document field :name belongs_to :label, touch: true end
您还可以使用 touch 选项,以字符串或符号的形式指定父关联上的另一个字段。 当 Mongoid 接触父关联时,它会将 updated_at字段和指定字段设置为当前日期和时间。
以下示例指示 Mongoid 触摸 bands_updated_at字段:
class Band include Mongoid::Document belongs_to :label, touch: :bands_updated_at end
注意
在嵌入式关联中,当触及嵌入式文档时,Mongoid 会递归触及其父文档。 因此,无需向 embedded_in 关联添加 touch 属性。
Mongoid 不支持在 embedded_in 关联中指定要触及的其他字段。
计数器缓存
使用 counter_cache 选项存储属于关联字段的对象数量。当您指定此选项时,Mongoid 会在关联的父模型上存储一个计数器字段。
counter_cache 选项接受以下两个计数器字段名称值之一:
true:Mongoid 使用基于关联名称的默认计数器字段名称。示例,如果关联名为Band,默认计数器字段名称为bands_count。string或symbol:Mongoid 直接使用该值作为计数器字段名称。
您可以通过以下方式之一为计数器缓存提供容器:
在父模型中定义显式计数器字段。
在父模型中包含
Mongoid::Attributes::Dynamic模块,这允许 Mongoid 在首次使用时动态创建计数器字段。
显式计数器字段
在此方法中,直接在父模型上声明计数器字段。当您需要固定模式和显式字段类型时,这非常有用。
以下示例在 Label 类上声明显式 bands_count字段,并使用默认计数器字段名称:
class Band include Mongoid::Document # Uses the default counter field name: bands_count belongs_to :label, counter_cache: true end class Label include Mongoid::Document field :bands_count, type: Integer has_many :bands end
您还可以通过将字符串传递给 counter_cache 选项来指定自定义计数器字段名称:
class Band include Mongoid::Document belongs_to :label, counter_cache: :active_bands_count end class Label include Mongoid::Document field :active_bands_count, type: Integer has_many :bands end
动态计数器字段
如果您在父模型中包含 Mongoid::Attributes::Dynamic 模块,则 Mongoid 可以在运行时动态创建和更新计数器字段。当您需要灵活的模式并且不想显式声明计数器字段时,这非常有用。
以下示例使用 Mongoid::Attributes::Dynamic 允许 Mongoid 在 Label 类上动态创建计数器字段:
class Band include Mongoid::Document belongs_to:label, counter_cache: true end class Label include Mongoid::Document include Mongoid::Attributes::Dynamic has_many :bands end
当您使用 Mongoid::Attributes::Dynamic 模块时,Mongoid 对计数器字段使用与声明显式计数器字段时相同的命名规则:它默认为基于关联的 *_count字段名称,除非您指定自定义字段名称counter_cache 选项中的一个字符串。