文档菜单
文档首页
/ / /
Mongoid
/

关联

在本页

  • 关联引用
  • Has One
  • Has Many
  • Belongs To
  • Has And Belongs To Many
  • 查询关联引用
  • 内嵌关联
  • Embeds One
  • Embeds Many
  • 递归嵌入
  • 引用与嵌入
  • 查询嵌入关联
  • 省略_id字段
  • 删除
  • 哈希分配
  • 常见行为
  • 扩展
  • 自定义关联名称
  • 自定义主键与外键
  • 自定义作用域
  • 验证
  • 多态
  • 级联回调
  • 依赖行为
  • 自动保存
  • 存在谓词
  • 自动构建
  • 修改
  • counter_cache选项
  • 关联代理
  • 关联元数据
  • 属性
  • 关联对象

Mongoid支持ActiveRecord用户熟悉的has_onehas_manybelongs_tohas_and_belongs_to_many关联。

使用has_one宏来声明父级在单独的集合中存储一个子级。默认情况下,子级是可选的

class Band
include Mongoid::Document
has_one :studio
end

使用has_one时,子模型必须使用belongs_to来声明与父级的关联

class Studio
include Mongoid::Document
belongs_to :band
end

根据上述定义,每个子文档都包含对其相应父文档的引用

band = Band.create!(studio: Studio.new)
# => #<Band _id: 600114fa48966848ad5bd392, >
band.studio
# => #<Studio _id: 600114fa48966848ad5bd391, band_id: BSON::ObjectId('600114fa48966848ad5bd392')>

使用验证来要求子级存在

class Band
include Mongoid::Document
has_one :studio
validates_presence_of :studio
end

使用has_many关联来声明父对象可以拥有零个或多个存储在单独集合中的子对象

class Band
include Mongoid::Document
has_many :members
end

has_one类似,子模型必须使用belongs_to来声明与父对象的关联

class Member
include Mongoid::Document
belongs_to :band
end

has_one类似,子文档包含对其相应父对象的引用

band = Band.create!(members: [Member.new])
# => #<Band _id: 6001166d4896684910b8d1c5, >
band.members
# => [#<Member _id: 6001166d4896684910b8d1c6, band_id: BSON::ObjectId('6001166d4896684910b8d1c5')>]

使用验证来确保至少有一个子对象存在

class Band
include Mongoid::Document
has_many :members
validates_presence_of :members
end

在关联上使用any?方法可以有效地确定关联是否包含任何文档,而不需要从数据库中检索整个文档集

band = Band.first
band.members.any?

any?还实现了Enumerable#any? API,允许使用代码块进行过滤

band = Band.first
band.members.any? { |member| member.instrument == 'piano' }

... 或者通过类名,这在处理多态关联时可能很有用

class Drummer < Member
end
band = Band.first
band.members.any?(Drummer)

如果关联已经加载,any? 将检查已加载的文档,而不会查询数据库

band = Band.first
# Queries the database
band.members.any?
band.members.to_a
# Does not query the database
band.members.any?

请注意,仅调用 any? 不会加载关联(因为 any? 只检索第一个匹配文档的 _id 字段)。

关联上的 exists? 方法确定关联中是否有任何 持久化 文档。与 any? 方法不同

  • exists? 总是查询数据库,即使关联已经加载。

  • exists? 不考虑未持久化的文档。

  • exists? 不像 any? 一样允许在应用程序中进行过滤,也不接受任何参数。

以下示例说明了 exists?any? 之间的区别

band = Band.create!
# Member is not persisted.
band.members.build
band.members.any?
# => true
band.members.exists?
# => false
# Persist the member.
band.members.map(&:save!)
band.members.any?
# => true
band.members.exists?
# => true

使用 belongs_to 宏来关联存储在单独集合中的父对象。如果有关联父对象,则存储子对象的 _id

默认情况下,如果在模型上定义了 belongs_to 关联,则必须在模型实例上提供值才能保存。使用 optional: true` 选项来使实例可持久化,而不指定父对象

class Band
include Mongoid::Document
has_one :studio
end
class Studio
include Mongoid::Document
belongs_to :band, optional: true
end
studio = Studio.create!
# => #<Studio _id: 600118184896684987aa884f, band_id: nil>

要更改 belongs_to 关联的默认行为,使其在全局上不要求相应的父对象,请将 belongs_to_required_by_default配置选项 设置为 false

尽管 has_onehas_many 关联需要子对象上定义相应的 belongs_to 关联,但 belongs_to 也可以在没有相应 has_onehas_many 宏的情况下使用。在这种情况下,子对象无法从父对象访问,但父对象可以从子对象访问

class Band
include Mongoid::Document
end
class Studio
include Mongoid::Document
belongs_to :band
end

为了清晰起见,可以在父对象没有定义关联的情况下添加 inverse_of: nil 选项

class Band
include Mongoid::Document
end
class Studio
include Mongoid::Document
belongs_to :band, inverse_of: nil
end

使用 has_and_belongs_to_many 宏来声明多对多关联

class Band
include Mongoid::Document
has_and_belongs_to_many :tags
end
class Tag
include Mongoid::Document
has_and_belongs_to_many :bands
end

如果有的话,两个模型实例都存储关联模型的 id 列表

band = Band.create!(tags: [Tag.create!])
# => #<Band _id: 60011d554896684b8b910a2a, tag_ids: [BSON::ObjectId('60011d554896684b8b910a29')]>
band.tags
# => [#<Tag _id: 60011d554896684b8b910a29, band_ids: [BSON::ObjectId('60011d554896684b8b910a2a')]>]

您可以使用 inverse_of: nil 选项创建单侧 has_and_belongs_to_many 关联,以仅在一个文档中存储 id

class Band
include Mongoid::Document
has_and_belongs_to_many :tags, inverse_of: nil
end
class Tag
include Mongoid::Document
end
band = Band.create!(tags: [Tag.create!])
# => #<Band _id: 60011dbc4896684bbbaa9255, tag_ids: [BSON::ObjectId('60011dbc4896684bbbaa9254')]>
band.tags
# => [#<Tag _id: 60011dbc4896684bbbaa9254, >]

单侧的has_and_belongs_to_many关联自然只能在定义它的模型中使用。

注意

给定两个模型A和B,其中Ahas_and_belongs_to_manyB,当向类型为A的文档上添加类型为B的文档到HABTM关联时,Mongoid不会更新类型为A的文档的updated_at字段,但会更新类型为B的文档的updated_at字段。

在大多数情况下,使用聚合管道进行跨引用关联的查询(以及一般涉及数据或条件或多个集合的查询)是高效的。关于构建聚合管道查询的Mongoid辅助函数在聚合管道部分中描述。

对于简单查询,可以避免使用聚合管道,并直接查询关联。当直接查询关联时,所有条件必须仅在该关联的集合上(这通常意味着相关的关联及其嵌入的任何关联)。

例如,给定以下模型

class Band
include Mongoid::Document
has_many :tours
has_many :awards
field :name, type: String
end
class Tour
include Mongoid::Document
belongs_to :band
field :year, type: Integer
end
class Award
include Mongoid::Document
belongs_to :band
field :name, type: String
end

可以通过以下方式检索自2000年以来巡演的所有乐队

band_ids = Tour.where(year: {'$gte' => 2000}).pluck(:band_id)
bands = Band.find(band_ids)

Tour的条件可以是任意复杂的,但它们都必须在同一个Tour文档(或嵌入在Tour中的文档)上。

要找到自2000年以来巡演的乐队的奖项

band_ids = Tour.where(year: {'$gte' => 2000}).pluck(:band_id)
awards = Award.where(band_id: {'$in' => band_ids})

感谢MongoDB的文档模型,Mongoid也提供了嵌入式关联,允许不同类型的文档以层次结构存储在同一个集合中。嵌入式关联使用embeds_oneembeds_manyembedded_in宏定义,以及用于递归嵌入的recursively_embeds_onerecursively_embeds_many

在父文档中嵌入子文档的一对一关联使用Mongoid的embeds_oneembedded_in宏定义。

关联的父文档应使用embeds_one宏来表示它有一个嵌入的子文档,而嵌入的文档使用embedded_in。为了正确工作,关联双方都需要有定义。

class Band
include Mongoid::Document
embeds_one :label
end
class Label
include Mongoid::Document
field :name, type: String
embedded_in :band
end

使用embeds_one宏嵌入的文档在父文档的数据库集合中以哈希形式存储。

{
"_id" : ObjectId("4d3ed089fb60ab534684b7e9"),
"label" : {
"_id" : ObjectId("4d3ed089fb60ab534684b7e0"),
"name" : "Mute",
}
}

您可以选择性地告诉Mongoid将嵌入的文档存储在除名称之外的其他属性中,通过提供:store_as选项。

class Band
include Mongoid::Document
embeds_one :label, store_as: "lab"
end

使用Mongoid的embeds_manyembedded_in宏定义一个从父文档到子文档的“一对多”关系。

关联的父文档应使用embeds_many宏来表示它有多个嵌入的子文档,其中嵌入的文档使用embedded_in。为了正确工作,关联的双方都需要定义。

class Band
include Mongoid::Document
embeds_many :albums
end
class Album
include Mongoid::Document
field :name, type: String
embedded_in :band
end

使用 embeds_many 宏嵌入的文档存储在父文档数据库集合中,以哈希数组的形式。

{
"_id" : ObjectId("4d3ed089fb60ab534684b7e9"),
"albums" : [
{
"_id" : ObjectId("4d3ed089fb60ab534684b7e0"),
"name" : "Violator",
}
]
}

您可以选择性地告诉Mongoid将嵌入的文档存储在除名称之外的其他属性中,通过提供:store_as选项。

class Band
include Mongoid::Document
embeds_many :albums, store_as: "albs"
end

一个文档可以通过使用 recursively_embeds_onerecursively_embeds_many 递归地嵌入自身,这提供了通过 parent_child_ 方法访问父文档和子文档的访问器。

class Tag
include Mongoid::Document
field :name, type: String
recursively_embeds_many
end
root = Tag.new(name: "programming")
child_one = root.child_tags.build
child_two = root.child_tags.build
root.child_tags # [ child_one, child_two ]
child_one.parent_tag # [ root ]
child_two.parent_tag # [ root ]
class Node
include Mongoid::Document
recursively_embeds_one
end
root = Node.new
child = Node.new
root.child_node = child
root.child_node # child
child.parent_node # root

虽然引用与嵌入的完整讨论超出了本教程的范围,但以下是一些选择其中一个而不是另一个的高层次考虑因素。

当关联被嵌入时,父文档和子文档都存储在同一个集合中。这允许在两者都使用/需要时进行高效的数据持久化和检索。例如,如果网站上的导航栏显示存储在文档中的用户属性,通常使用嵌入关联是一个好主意。

使用嵌入关联可以使用MongoDB工具(如聚合管道)以强大的方式查询这些文档。

由于嵌入文档作为其父顶级文档的一部分存储,因此无法单独持久化嵌入文档,也无法直接检索嵌入文档。但是,在MongoDB投影操作的帮助下,嵌入文档仍然可以有效地进行查询和检索。

class Band
include Mongoid::Document
field :started_on, type: Date
embeds_one :label
end
class Label
include Mongoid::Document
field :name, type: String
embedded_in :band
end
# Retrieve labels for bands started in the last year.
#
# Sends a find query like this:
# {"find"=>"bands",
# "filter"=>{"started_on"=>{"$gt"=>2018-07-01 00:00:00 UTC}},
# "projection"=>{"_id"=>1, "label"=>1}}
Band.where(started_on: {'$gt' => Time.now - 1.year}).only(:label).map(&:label).compact.uniq

将过时值设置为引用关联有时会导致数据库中持久化一个 nil 值。以下是一个例子

class Post
include Mongoid::Document
has_one :comment, inverse_of: :post
end
class Comment
include Mongoid::Document
belongs_to :post, inverse_of: :comment, optional: true
end
post.comment = comment1
post.reload

在此处,post.comment 被设置为 comment1,然而由于发生了重新加载,post.comment 并不指向与 comment1 相同的对象。这意味着,更新一个对象不会隐式地更新另一个对象。这对于下一个操作很重要

post.comment = comment2
post.reload

现在,post.comment 被设置为 comment2,而旧评论的 post_id 被设置为 nil。然而,分配给 post.comment 的值并没有指向与 comment1 相同的对象,因此,虽然 post.comment 的旧值被更新为具有 nilpost_id,但 comment1 仍然设置了 post_id

post.comment = comment1
post.reload

最后,这个最后的赋值尝试将 comment1 上的 post_id 设置为 nil,此时应该为 nil,但设置为旧的 post_id。在此操作期间,从 comment2 中清除 post_id,并在 comment1 上设置新的 post_id。然而,由于 comment1 上已经设置了 post_id,因此没有进行持久化,我们最终得到两个评论都有一个 nilpost_id。在此阶段,运行 post.comment 返回 nil

在查询顶级文档时,可以通过点表示法在嵌入式关联中的文档上指定条件。例如,给定以下模型

class Band
include Mongoid::Document
embeds_many :tours
embeds_many :awards
field :name, type: String
end
class Tour
include Mongoid::Document
embedded_in :band
field :year, type: Integer
end
class Award
include Mongoid::Document
embedded_in :band
field :name, type: String
end

要基于巡回属性检索乐队,请使用以下点表示法

# Get all bands that have toured since 2000
Band.where('tours.year' => {'$gte' => 2000})

要仅检索嵌入式关联的文档,而不检索顶级文档,请使用pluck投影方法

# Get awards for bands that have toured since 2000
Band.where('tours.year' => {'$gte' => 2000}).pluck(:awards)

Mongoid查询方法可以用于在应用程序中已加载的文档的嵌入式关联上。这种机制称为“嵌入式匹配”,它完全由Mongoid实现——查询不会发送到服务器。

支持以下运算符

例如,使用前面给出的模型定义,我们可以查询已加载乐队上的巡回

band = Band.where(name: 'Astral Projection').first
tours = band.tours.where(year: {'$gte' => 2000})

Mongoid的嵌入式匹配旨在支持与最新MongoDB服务器版本上的原生查询相同的功能和语义。请注意以下已知限制

  • 嵌入式匹配未实现全文搜索地理空间查询运算符、执行JavaScript代码的运算符($where)以及通过其他服务器功能(如$expr$jsonSchema.)实现的运算符。

  • 蒙古鸟 DSL 将 Range 参数扩展为具有 $gte$lte 条件的哈希表。在某些情况下,这会创建无效的查询。嵌套匹配器在这些情况下引发 InvalidQuery 异常。已知受影响的操作符包括 $elemMatch$eq$gt$gte$lt$lte$ne

  • 当使用 $regex 进行嵌套匹配时,目前无法 将正则表达式对象指定为模式,并提供选项。

  • MongoDB 服务器 4.0 及更早版本不严格验证 $type 参数(例如,允许无效参数如 0)。客户端端进行更严格的验证。

默认情况下,Mongoid 会为每个嵌入文档添加一个 _id 字段。这允许轻松引用和操作嵌入文档。

可以省略这些 _id 字段以节省存储空间。要这样做,请在子文档中覆盖 _id 字段定义并移除默认值。

class Order
include Mongoid::Document
embeds_many :line_items
end
class LineItem
include Mongoid::Document
embedded_in :order
field :_id, type: Object
end

在当前版本的 Mongoid 中,字段定义是必需的,但没有指定默认值时,数据库中不会存储任何值。Mongoid 的未来版本可能会允许删除先前定义的字段。

注意

移除 _id 字段意味着在查询、更新和删除操作中必须通过嵌入文档的内容属性值来识别它们。

Mongoid 提供了三种从 embeds_many 关联中删除子项的方法:cleardestroy_alldelete_all

clear 方法使用 $unset 操作符 从主文档中删除整个关联。它不会在正在删除的文档上运行 destroy 回调,在这方面类似于 delete_all

band = Band.find(...)
band.tours.clear

如果在一个未保存的主文档的关联上调用 clear,它仍会尝试根据主文档的 _id 从数据库中删除关联。

band = Band.find(...)
band.tours << Tour.new(...)
unsaved_band = Band.new(id: band.id, tours: [Tour.new])
# Removes all tours from the persisted band due to _id match.
unsaved_band.tours.clear
band.tours
# => []

delete_all 方法使用 $pullAll 操作符 删除关联中的文档。与 clear 不同,delete_all

  • 如果尚未加载,将加载关联;

  • 仅删除应用程序中存在的文档。

delete_all 不在要删除的文档上运行销毁回调。

示例

band = Band.find(...)
band.tours.delete_all

delete_all 方法在运行销毁回调的同时使用 $pullAll 操作符 删除关联中的文档。与 delete_all 相似,destroy_all 如果尚未加载,则加载整个关联,并且仅删除应用程序中存在的文档

band = Band.find(...)
band.tours.destroy_all

嵌入式关联允许用户将 哈希 而不是文档赋值给关联。在赋值时,此哈希被强制转换为要分配给关联的类的文档。以下是一个示例

class Band
include Mongoid::Document
embeds_many :albums
end
class Album
include Mongoid::Document
field :name, type: String
embedded_in :band
end
band = Band.create!
band.albums = [ { name: "Narrow Stairs" }, { name: "Transatlanticism" } ]
p band.albums
# => [ #<Album _id: 633c71e93282a4357bb608e5, name: "Narrow Stairs">, #<Album _id: 633c71e93282a4357bb608e6, name: "Transatlanticism"> ]

这适用于 embeds_oneembeds_manyembedded_in 关联。请注意,您不能将哈希分配给引用的关联。

所有关联都可以有扩展,这提供了一种向关联添加特定于应用程序功能的方法。它们通过向关联定义提供块来定义。

class Person
include Mongoid::Document
embeds_many :addresses do
def find_by_country(country)
where(country: country).first
end
def chinese
_target.select { |address| address.country == "China" }
end
end
end
person.addresses.find_by_country("Mongolia") # returns address
person.addresses.chinese # returns [ address ]

您可以为您的关联命名,但如果类名无法从名称中推断出来,也无法从相反的一侧推断出来,那么您需要向宏提供一些附加选项,以告诉 Mongoid 如何将它们连接起来。

class Car
include Mongoid::Document
embeds_one :engine, class_name: "Motor", inverse_of: :machine
end
class Motor
include Mongoid::Document
embedded_in :machine, class_name: "Car", inverse_of: :engine
end

用于查找关联的字段可以显式指定。默认情况下,使用“父”关联上的id和“子”关联上的#{association_name}_id,例如在has_many/belongs_to中使用

class Company
include Mongoid::Document
has_many :emails
end
class Email
include Mongoid::Document
belongs_to :company
end
company = Company.find(id)
# looks up emails where emails.company_id == company.id
company.emails

指定不同的primary_key可以更改“父”关联的字段名,以及foreign_key可以更改“子”关联的字段名

class Company
include Mongoid::Document
field :c, type: String
has_many :emails, foreign_key: 'c_ref', primary_key: 'c'
end
class Email
include Mongoid::Document
# This definition of c_ref is automatically generated by Mongoid:
# field :c_ref, type: Object
# But the type can also be specified:
field :c_ref, type: String
belongs_to :company, foreign_key: 'c_ref', primary_key: 'c'
end
company = Company.find(id)
# looks up emails where emails.c_ref == company.c
company.emails

在has_and_belongs_to_many关联中,由于数据存储在关联的两边,当定义关联时,有4个字段可以配置

  • :primary_key是远程模型中包含远程模型查找值的字段。

  • :foreign_key是本地模型中存储:primary_key值的字段。

  • :inverse_primary_key是远程模型用于查找本地模型文档的本地模型中的字段。

  • :inverse_foreign_key是存储在:inverse_primary_key中的值的远程模型字段。

一个例子可能会使这更清晰

class Company
include Mongoid::Document
field :c_id, type: Integer
field :e_ids, type: Array
has_and_belongs_to_many :employees,
primary_key: :e_id, foreign_key: :e_ids,
inverse_primary_key: :c_id, inverse_foreign_key: :c_ids
end
class Employee
include Mongoid::Document
field :e_id, type: Integer
field :c_ids, type: Array
has_and_belongs_to_many :companies,
primary_key: :c_id, foreign_key: :c_ids,
inverse_primary_key: :e_id, inverse_foreign_key: :e_ids
end
company = Company.create!(c_id: 123)
# => #<Company _id: 5c565ece026d7c461d8a9d4e, c_id: 123, e_ids: nil>
employee = Employee.create!(e_id: 456)
# => #<Employee _id: 5c565ee8026d7c461d8a9d4f, e_id: 456, c_ids: nil>
company.employees << employee
company
# => #<Company _id: 5c565ece026d7c461d8a9d4e, c_id: 123, e_ids: [456]>
employee
# => #<Employee _id: 5c5883ce026d7c4b9e244c0c, e_id: 456, c_ids: [123]>

注意,与默认的#{association_name}_id字段一样,Mongoid会自动为自定义外部键c_ref在模型中添加一个字段。然而,由于Mongoid不知道字段中应该允许哪种类型的数据,该字段以Object类型创建。明确定义合适的字段类型是个好主意。

您可以使用:scope参数在关联上设置特定的范围。范围是一个额外的过滤器,它限制了哪些对象被认为是关联的一部分 - 范围关联将仅返回满足范围条件的文档。范围可以是

  • 一个零参数的Proc,或者

  • 一个引用关联模型中命名范围的 Symbol

class Trainer
has_many :pets, scope: -> { where(species: 'dog') }
has_many :toys, scope: :rubber
end
class Pet
belongs_to :trainer
end
class Toy
scope :rubber, where(material: 'rubber')
belongs_to :trainer
end

注意

可以将不满足关联范围要求的文档添加到该关联中。在这种情况下,这些文档将在内存中显示为关联,并将保存到数据库中,但在将来查询关联时将不会存在。例如

trainer = Trainer.create!
dog = Pet.create!(trainer: trainer, species: 'dog')
cat = Pet.create!(trainer: trainer, species: 'cat')
trainer.pets #=> [dog, cat]
trainer.reload.pets #=> [dog]

注意

Mongoid 的作用域关联语法与 ActiveRecord 不同。Mongoid 使用 :scope 关键字参数以与其他关联选项保持一致,而在 ActiveRecord 中,范围是位置参数。

需要注意的是,默认情况下,Mongoid 将通过 validates_associated 验证通过内存加载的任何关联的子项。此操作适用的关联包括

  • embeds_many

  • embeds_one

  • has_many

  • has_one

  • has_and_belongs_to_many

如果您不希望有此行为,可以在定义关联时将其关闭。

class Person
include Mongoid::Document
embeds_many :addresses, validate: false
has_many :posts, validate: false
end

一对一和一对多关联支持多态性,即单个关联可能包含不同类的对象。例如,我们可以这样模拟一个组织,其中部门和团队有经理:

class Department
include Mongoid::Document
has_one :manager, as: :unit
end
class Team
include Mongoid::Document
has_one :manager, as: :unit
end
class Manager
include Mongoid::Document
belongs_to :unit, polymorphic: true
end
dept = Department.create!
team = Team.create!
alice = Manager.create!(unit: dept)
alice.unit == dept
# => true
dept.manager == alice
# => true

为了提供另一个例子,假设我们想要跟踪产品的价格历史和捆绑包。这可以通过嵌入的一对多多态关联实现

class Product
include Mongoid::Document
field :name, type: String
has_and_belongs_to_many :bundles
embeds_many :prices, as: :item
end
class Bundle
include Mongoid::Document
field :name, type: String
has_and_belongs_to_many :products
embeds_many :prices, as: :item
end
class Price
include Mongoid::Document
embedded_in :item, polymorphic: true
end
pants = Product.create!(name: 'Pants',
prices: [Price.new, Price.new])
costume = Bundle.create!(name: 'Costume', products: [pants],
prices: [Price.new, Price.new])

要定义多态关联,请在子关联上指定 polymorphic: true 选项,并在父关联上添加 as: :association_name 选项。

请注意,Mongoid 目前仅在从子到父的方向上支持多态性。例如,不能使用多态性来指定捆绑包可以包含其他捆绑包或产品。

class Bundle
include Mongoid::Document
# Does not work:
has_many :items, polymorphic: true
end

has_and_belongs_to_many 关联不支持多态。

如果您希望在父对象上进行持久化操作时触发嵌入文档的回调,则需要将级联回调选项提供给关联。

class Band
include Mongoid::Document
embeds_many :albums, cascade_callbacks: true
embeds_one :label, cascade_callbacks: true
end
band.save # Fires all save callbacks on the band, albums, and label.

您可以为引用的关联提供依赖性选项,以指导 Mongoid 如何处理关联的一侧被删除或尝试删除的情况。选项如下

  • :delete_all:删除子文档而不运行任何模型回调。

  • :destroy:销毁子文档并运行所有模型回调。

  • :nullify:将子文档的外键字段设置为 nil。如果子文档通常仅通过父文档引用,则子文档可能成为孤儿。

  • :restrict_with_exception:如果子文档不为空,则抛出错误。

  • :restrict_with_error:如果子文档不为空,则取消操作并返回 false。

如果没有提供 :dependent 选项,删除父文档将不会修改子文档(换句话说,子文档将继续通过外键字段引用已删除的父文档)。如果子文档通常仅通过父文档引用,则子文档可能成为孤儿。

class Band
include Mongoid::Document
has_many :albums, dependent: :delete_all
belongs_to :label, dependent: :nullify
end
class Album
include Mongoid::Document
belongs_to :band
end
class Label
include Mongoid::Document
has_many :bands, dependent: :restrict_with_exception
end
label = Label.first
label.bands.push(Band.first)
label.delete # Raises an error since bands is not empty.
Band.first.destroy # Will delete all associated albums.

与ActiveRecord相比,Mongoid的一个核心区别是,出于性能考虑,Mongoid在保存父文档时不会自动保存引用的(即非嵌入)关联文档。

如果不使用自动保存,则可能通过关联创建悬空引用到不存在的文档。

class Band
include Mongoid::Document
has_many :albums
end
class Album
include Mongoid::Document
belongs_to :band
end
band = Band.new
album = Album.create!(band: band)
# The band is not persisted at this point.
album.reload
album.band_id
# => BSON::ObjectId('6257699753aefe153121a3d5')
# Band does not exist.
album.band
# => nil

要使引用关联在保存父文档时自动保存,请在关联中添加: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.
album.reload
album.band_id
# => BSON::ObjectId('62576b4b53aefe178b65b8e3')
album.band
# => #<Band _id: 62576b4b53aefe178b65b8e3, >

当使用accepts_nested_attributes_for时,会自动将自动保存功能添加到关联中,这样应用程序在处理表单提交时不需要跟踪哪些关联已被修改。

嵌入式关联始终自动保存,因为它们作为父文档的一部分存储。

某些关联操作始终将父文档和子文档作为操作的一部分保存,无论是否启用自动保存。以下是一些这类操作的列表,但不限于:

  • 关联赋值

    # Saves the band and the album.
    band.albums = [Album.new]
  • push<<

    band.albums << Album.new
    band.albums.push(Album.new)

所有关联都包含存在谓词,形式为name?has_name?,以检查关联是否为空。

class Band
include Mongoid::Document
embeds_one :label
embeds_many :albums
end
band.label?
band.has_label?
band.albums?
band.has_albums?

一对一关联(embeds_onehas_one)有一个自动构建选项,告诉Mongoid在访问关联且其为nil时实例化一个新文档。

class Band
include Mongoid::Document
embeds_one :label, autobuild: true
has_one :producer, autobuild: true
end
band = Band.new
band.label # Returns a new empty label.
band.producer # Returns a new empty producer.

任何 belongs_to 关联都可以选择性地使用 :touch 选项,这样当子文档更新时,父文档也会被修改。

class Band
include Mongoid::Document
field :name
belongs_to :label, touch: true
end
band = Band.first
band.name = "The Rolling Stones"
band.save! # Calls touch on the parent label.
band.touch # Calls touch on the parent label.

:touch 还可以接受一个字符串或符号参数,指定在父关联中需要修改的字段,除了 updated_at 以外。

class Label
include Mongoid::Document
include Mongoid::Timestamps
field :bands_updated_at, type: Time
has_many :bands
end
class Band
include Mongoid::Document
belongs_to :label, touch: :bands_updated_at
end
label = Label.create!
band = Band.create!(label: label)
band.touch # Updates updated_at and bands_updated_at on the label.

当一个嵌入文档被修改时,其父级会通过组合根递归地被修改(因为当嵌入文档被保存时,所有父级都必须保存)。因此,在 embedded_in 关联上不需要设置 :touch 属性。

Mongoid 目前 不支持在 embedded_in 关联上指定要修改的额外字段

embedded_in 关联上不应将 :touch 设置为 false,因为嵌入文档的修改会导致组合层次结构始终被更新。这目前尚未实施,但未来将实施这项措施

与ActiveRecord一样,可以在关联上使用:counter_cache选项,以提高查找属于对象数量的效率。同样,类似于ActiveRecord,必须考虑到关联模型将有一个额外的属性。这意味着在使用Mongoid时,需要在关联模型中包含Mongoid::Attributes::Dynamic。例如

class Order
include Mongoid::Document
belongs_to :customer, counter_cache: true
end
class Customer
include Mongoid::Document
include Mongoid::Attributes::Dynamic
has_many :orders
end

关联使用对目标对象的透明代理。这可能在某些情况下导致意外的行为。

当访问关联目标上的方法时,可能会丢失方法可见性,这取决于关联

class Order
include Mongoid::Document
belongs_to :customer
private
def internal_status
'new'
end
end
class Customer
include Mongoid::Document
has_many :orders
private
def internal_id
42
end
end
order = Order.new
customer = Customer.create!(orders: [order])
# has_many does not permit calling private methods on the target
customer.orders.first.internal_status
# NoMethodError (private method `internal_status' called for #<Order:0x000055af2ec46c50>)
# belongs_to permits calling private methods on the target
order.customer.internal_id
# => 42

Mongoid中所有的关联都包含有关所讨论关联的元数据,这对于第三方开发者使用扩展Mongoid来说是一个有价值的工具。

您可以通过几种不同的方式访问关联的元数据。

# Get the metadata for a named association from the class or document.
Model.reflect_on_association(:association_name)
model.reflect_on_association(:association_name)
# Get the metadata with a specific association itself on a specific
# document.
model.associations[:association_name]

所有关联都包含一个_target,这是代理文档或文档,一个_base,这是关联所依赖的文档,以及_association,它提供了关于关联的信息。

class Person
include Mongoid::Document
embeds_many :addresses
end
person.addresses = [ address ]
person.addresses._target # returns [ address ]
person.addresses._base # returns person
person.addresses._association # returns the association metadata

关联对象本身包含比人们可能知道更多的信息,对于Mongoid的扩展开发人员来说很有用。

方法
描述
Association#as
返回多态子类的父类名称。
Association#as?
返回是否存在as选项。
Association#autobuilding?
返回关联是否自动构建。
Association#autosaving?
返回关联是否自动保存。
Association#cascading_callbacks?
返回关联是否从父类向下级联回调。
Association#class_name
返回代理文档的类名。
Association#cyclic?
返回关联是否是循环关联。
Association#dependent
返回关联的依赖选项。
Association#destructive?
如果关联具有依赖删除或销毁,则返回true。
Association#embedded?
返回关联是否嵌入另一个文档中。
Association#forced_nil_inverse?
返回关联是否定义了nil反向。
Association#foreign_key
返回外键字段名称。
Association#foreign_key_check
返回外键字段脏检查方法名称。
Association#foreign_key_setter
返回外键字段设置器名称。
Association#indexed?
返回外键是否自动索引。
Association#inverses
返回所有反向关联的名称。
Association#inverse
返回单个反向关联的名称。
Association#inverse_class_name
返回反向关联侧的关联类名。
Association#inverse_foreign_key
返回反向关联侧的外键字段名称。
Association#inverse_klass
返回反向关联侧的关联类。
Association#inverse_association
返回反向关联侧的关联元数据。
Association#inverse_of
返回显式定义的反向关联的名称。
Association#inverse_setter
返回用于设置反向关联的方法名称。
Association#inverse_type
返回反向关联的多态类型字段名称。
Association#inverse_type_setter
返回反向关联的多态类型字段设置器名称。
Association#key
返回用于获取关联的属性哈希中的字段名称。
Association#klass
返回关联中代理文档的类。
Association#name
返回关联名称。
Association#options
返回self,以与ActiveRecord API保持兼容。
Association#order
返回关联上的自定义排序选项。
Association#polymorphic?
返回关联是否是多态的。
Association#setter
返回设置关联的字段名称。
Association#store_as
返回存储嵌入式关联的属性名称。
Association#touchable?
返回关联是否有touch选项。
Association#type
返回获取多态类型的字段名称。
Association#type_setter
返回设置多态类型的字段名称。
Association#validate?
返回关联是否有关联的验证。

返回

继承