Make the MongoDB docs better! We value your opinion. Share your feedback for a chance to win $100.
Click here >
Docs 菜单
Docs 主页
/ /

自定义关联行为

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_keyforeign_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_keyinverse_foreign_key 宏。 inverse_primary_key 宏指定本地模型上远程模型用于查找文档的字段。 inverse_foreign_key 宏指定远程模型上用于存储在 inverse_primary_key 中找到的值的字段。

以下示例为 has_and_belongs_to_many 关联中的 BandMembers 类指定了新的主节点 (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_many

  • embeds_one

  • has_many

  • has_one

  • has_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 关联可以包含 LabelAlbum文档。

重要

Mongoid 仅支持从子节点到父节点的多态性。 您不能将父 has_onehas_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::BandTools::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_oneembeds_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

  • stringsymbol: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 选项中的一个字符串。

后退

嵌入式关联

在此页面上