Docs 菜单
Docs 主页
/ / /
Mongoid
/

执行数据操作

在此页面上

  • Overview
  • 创建操作
  • 创建!
  • 创建
  • save!
  • 保存
  • 读取操作
  • 属性
  • 重新加载
  • 更新操作
  • update_attributes!
  • update_attributes
  • update_attribute
  • 更新插入
  • touch
  • 删除操作
  • 删除
  • 销毁
  • delete_all
  • destroy_all
  • 持久性属性
  • new_record?
  • 持续存在?
  • 访问字段值
  • 获取和设置字段值
  • read_attribute 和 write_attribute
  • 批量写入属性
  • 原子更新操作符
  • 对原子操作进行分组
  • 脏跟踪
  • 查看更改
  • 重置更改
  • 持久性
  • 查看上一个的更改
  • 更新容器字段
  • 只读文档
  • 更多信息

在本指南中,您可以学习;了解如何使用 Mongoid 执行增删改查 (创建、读取、更新、删除)操作,以修改MongoDB集合中的数据。

Mongoid 支持增删改查操作,您可以使用其他Ruby映射器(例如 Active Record 或 Data Mapper)来执行这些操作。使用 Mongoid 时,一般持久性操作仅对您更改的字段执行原子更新,而不是像其他 ODM 那样每次都将整个文档写入数据库。

您可以执行创建操作以将新文档添加到集合。如果集合不存在,则该操作会隐式创建集合。以下部分描述了可用于创建新文档的方法。

使用模型类上的 create! 方法将一个或多个文档插入到集合中。如果出现任何服务器或验证错误,create! 会引发异常。

要调用 create!,请传递定义要插入的文档的属性哈希。如果要创建并插入多个文档,请传递一个哈希大量。

此示例展示了调用 create! 的多种方法。第一个示例创建一个 Person文档,第二个示例创建两个 Person 文档。第三个示例将 do..end区块传递给 create!。 Mongoid 使用作为参数传递给 create! 的文档来调用此区块。 create! 方法尝试在区块末尾保存文档:

Person.create!(
first_name: "Heinrich",
last_name: "Heine"
)
Person.create!([
{ first_name: "Heinrich", last_name: "Heine" },
{ first_name: "Willy", last_name: "Brandt" }
])
Person.create!(first_name: "Heinrich") do |doc|
doc.last_name = "Heine"
end

使用 create 方法将一个新文档或多个新文档插入数据库。与 ! 后缀的版本不同,create 不会在验证错误时引发异常。 create 确实会因服务器错误而引发异常,例如插入具有重复 _id字段的文档。

如果 create 遇到任何验证错误,则不会插入该文档,而是与已插入的其他文档一起返回。您可以使用 persisted?new_record?errors 方法验证插入数据库的文档。

此示例演示如何使用 create 将新文档插入MongoDB。第一个示例展示了如何插入 Person文档。第二个示例尝试插入两个 Post 文档,但第二个文档未通过验证,因为它包含重复的标题。然后,该示例使用 persisted? 方法确认哪些文档已成功插入到集合中:

Person.create(
first_name: "Heinrich",
last_name: "Heine"
)
class Post
include Mongoid::Document
validates_uniqueness_of :title
end
posts = Post.create([{title: "test"}, {title: "test"}])
posts.map { |post| post.persisted? } # => [true, false]

要学习;了解有关persisted?new_record? 方法的更多信息,请参阅本指南的 持久性属性部分。

使用 save! 方法以原子方式将更改的属性保存到集合中或插入新文档。如果存在任何服务器或验证错误,save! 会引发异常。您可以使用 new 方法创建新的文档实例。然后,使用 save! 将文档插入数据库。

以下示例展示了如何使用 save! 插入新的 Person文档并更新现有文档的 first_name字段:

person = Person.new(
first_name: "Esmeralda",
last_name: "Qemal"
)
person.save!
person.first_name = "Malik"
person.save!

如果出现任何验证错误,save 方法不会引发异常。如果出现任何服务器错误,save 仍会引发异常。如果已保存所有更改的属性,则该方法会返回 true;如果出现任何验证错误,则该方法会返回 false

您可以将以下选项传递给 save

  • validate: false:保存新文档或更新后的属性时绕过验证。

  • touch: false:更新指定属性时不更新updated_at字段。插入新文档时,该选项无效。

以下代码使用 save 插入新文档。然后更新该文档并应用 validate: false 选项。

person = Person.new(
first_name: "Tamara",
last_name: "Graham"
)
person.save
person.first_name = "Aubrey"
person.save(validate: false)

您可以执行读取操作,从集合中检索文档。要学习;了解有关创建查询筛选器以检索文档子集的更多信息,请参阅 指定查询指南。

您可以使用 attributes 方法以哈希形式检索模型实例的属性。此哈希还包含所有嵌入式文档的属性。

以下示例展示了如何使用 attributes

person = Person.new(first_name: "James", last_name: "Nan")
person.save
puts person.attributes
{ "_id" => BSON::ObjectId('...'),
"first_name" => "James",
"last_name" => "Nan"
}

您可以使用 reload 方法从MongoDB访问权限文档的最新版本。当您重新加载文档时,Mongoid 还会重新加载同一查询中的所有嵌入式关联。但是,Mongoid 不会重新加载引用的关联。相反,它会清除这些值,以便在下次访问权限期间从数据库加载它们。

当您对文档调用 reload 时,任何未保存的对该文档的更改都将丢失。以下代码展示了如何对文档调用 reload

band = Band.create!(name: 'Sun 1')
# => #<Band _id: ..., name: "Sun 1">
band.name = 'Moon 2'
# => #<Band _id: ..., name: "Moon 2">
band.reload
# => #<Band _id: ..., name: "Sun 1">

前面的示例更新了 band文档上的 name字段,但不保存新值。由于 Mongoid 不会持久保存对 name 值的更改,因此 name 包含保存到数据库中的原始值。

注意

未找到文档错误

当 Mongoid 在数据库中找不到文档时,默认会引发 Mongoid::Errors::DocumentNotFound 错误。您可以在 mongoid.yml文件中将 raise_not_found_error设立选项设置为 false,以指示 Mongoid 保存新文档并将其属性设立为默认值。通常,它还会更改 _id字段的值。因此,当 raise_not_found_error设立为 false 时,我们不建议使用 reload

当您对未保留的文档调用 reload 时,该方法会对该文档的 _id 值执行 find查询。

以下示例对尚未保存的文档调用 reload,并打印出 name字段值。 reload 使用文档的 _id 值执行 find 操作,这会导致 Mongoid检索集合中的现有文档:

existing = Band.create!(name: 'Photek')
band = Band.new(id: existing.id)
band.reload
puts band.name
Photek

您可以执行更新操作来修改集合中的现有文档。如果您尝试更新已删除的文档,Mongoid 会引发 FrozenError 异常。

您可以使用 update_attributes! 方法更新现有模型实例的属性。如果遇到任何验证或服务器错误,此方法会引发异常。

以下示例展示如何使用 update_attributes!更新现有文档的 first_namelast_name 属性:

person.update_attributes!(
first_name: "Maximilian",
last_name: "Hjalmar"
)

提示

Mongoid 提供嵌套属性功能,允许您在一次调用中更新文档及其嵌套关联。要学习;了解更多信息,请参阅嵌套属性指南。

update_attributes 方法不会在验证错误时引发异常。如果该方法通过验证且文档已更新,则返回 true,否则返回 false

以下示例展示了如何使用 update_attributes

person.update_attributes(
first_name: "Hasan",
last_name: "Emine"
)

您可以使用update_attribute 方法绕过验证并更新模型实例的单个属性。

以下示例展示了如何使用 update_attribute更新文档的 first_name 属性的值:

person.update_attribute(:first_name, "Jean")

您可以使用 upsert 方法更新、插入或替换文档。

upsert 接受 replace 选项。如果将此选项设立为 true,并且调用 upsert 的文档已存在于数据库中,则新文档将替换数据库中的文档。数据库中新文档未替换的所有字段都将被删除。

如果将 replace 选项设立为 false 且该文档存在于数据库中,则会对其进行更新。除更新文档中指定的字段外,Mongoid 不会更改任何字段。如果数据库中不存在该文档,则会将其插入更新文档中指定的字段和值。默认下,replace 选项设立为 false

以下示例展示了如何使用 upsert 首先插入一个新文档,然后通过设置 replace: true 来替换它:

person = Person.new(
first_name: "Balu",
last_name: "Rama"
)
person.upsert
person.first_name = "Ananda"
person.upsert(replace: true)

您可以使用 touch 方法将文档的 updated_at 时间戳更新为当前时间。 touch 将更新级联到文档的任何 belongs_to 关联。您还可以传递另一个具有时间值的字段作为选项,以同时更新该字段。

以下示例使用 touch更新updated_ataudited_at 时间戳:

person.touch(:audited_at)

您可以执行删除操作,从集合中删除文档。

您可以使用 delete 方法从数据库中删除文档。当您使用 delete 时,Mongoid 不会运行任何回调。如果文档未保存到数据库,delete 会尝试删除任何具有相同 _id 值的文档。

以下示例展示了如何使用 delete 方法,并演示了删除未保存到数据库的文档时会发生什么情况:

person = Person.create!(name: 'Edna Park')
unsaved_person = Person.new(id: person.id)
unsaved_person.delete
person.reload

在前面的示例中,当您调用 reload 时,Mongoid 会引发 Mongoid::Errors::DocumentNotFound 错误,因为 unsaved_person.delete 删除了 person文档,因为这两个文档具有相同的 _id 值。

destroy 方法的运行方式与 delete 类似,不同之处在于 Mongoid 在调用 destroy 时运行回调。如果在数据库中找不到该文档,destroy 会尝试删除任何具有相同 _id 的文档。

以下示例展示了如何使用 destroy

person.destroy

delete_all 方法会删除集合中由 Mongoid 模型类建模的所有文档。 delete_all 不运行回调。

以下示例展示了如何使用 delete_all删除所有 Person 文档:

Person.delete_all

destroy_all 方法会删除集合中由 Mongoid 模型类建模的所有文档。这可能是一项成本高昂的操作,因为 Mongoid 会将所有文档加载到内存中。

以下示例展示了如何使用 destroy_all删除所有 Person 文档:

Person.destroy_all

以下部分介绍了 Mongoid 提供的属性,您可以使用这些属性来检查文档是否已持久保存到数据库中。

如果模型实例尚未保存到数据库,则 new_record?属性返回true ,否则返回false 。它检查是否存在与persisted? 属性相反的条件。

以下示例展示了如何使用 new_record?

person = Person.new(
first_name: "Tunde",
last_name: "Adebayo"
)
puts person.new_record?
person.save!
puts person.new_record?
true
false

如果 Mongoid 保留模型实例,则 persisted? 属性返回 true,否则返回 false。它检查是否存在与 new_record? 属性相反的条件。

以下示例展示了如何使用 persisted?

person = Person.new(
first_name: "Kiana",
last_name: "Kahananui"
)
puts person.persisted?
person.save!
puts person.persisted?
false
true

Mongoid 提供了多种访问权限文档字段值的方法。以下部分介绍如何访问权限字段值。

有多种方法可以获取和设立文档的字段值。如果显式声明一个字段,则可以直接在文档上获取和设立该字段值。以下示例展示如何设立和获取 Person实例的 first_name字段:

class Person
include Mongoid::Document
field :first_name
end
person = Person.new
person.first_name = "Artem"
person.first_name # => "Artem"

前面的示例首先使用 first_name 属性设立一个值,然后再次调用它以检索该值。

您还可以在 Mongoid 模型实例上使用 [][] = 方法,通过哈希语法访问权限属性。 [] 方法是 read_attribute 方法的别名,[] = 方法是 write_attribute 方法的别名。以下示例演示如何使用 [][]= 方法获取和设立具有别名的 first_name字段:

class Person
include Mongoid::Document
field :first_name, as: :fn
end
person = Person.new(first_name: "Artem")
person["fn"]
# => "Artem"
person[:first_name] = "Vanya"
# => "Artem"
person
# => #<Person _id: ..., first_name(fn): "Vanya">

要学习;了解有关这些方法的更多信息,请参阅本指南的以下 read_attribute 和 write_attribute 部分。

您可以使用 read_attributewrite_attribute 方法指定读取或写入字段时的自定义行为。您可以在定义模型时使用这些方法,也可以在模型实例上调用这些方法。

要使用 read_attribute 获取字段,请将该字段的名称传递给该方法。要使用 write_attribute设立字段,请传递字段名称和要分配的值。

以下示例在模型定义中使用 read_attributewrite_attributefirst_namefirst_name= 定义为用于读取和写入fn 属性的方法:

class Person
include Mongoid::Document
def first_name
read_attribute(:fn)
end
def first_name=(value)
write_attribute(:fn, value)
end
end
person = Person.new
person.first_name = "Artem"
person.first_name
# => "Artem"

您还可以直接对模型实例调用 read_attributewrite_attribute 来获取和设立属性。以下示例在模型实例上使用这些方法来获取 first_name 属性并将其设立为值 "Pushkin"

class Person
include Mongoid::Document
field :first_name, as: :fn
end
person = Person.new(first_name: "Artem")
# => #<Person _id: ..., first_name(fn): "Artem">
person.read_attribute(:first_name)
# => "Artem"
person.read_attribute(:fn)
# => "Artem"
person.write_attribute(:first_name, "Pushkin")
person
# => #<Person _id: ..., first_name(fn): "Pushkin">

您可以在模型实例上使用 attributes=write_attributes 方法,同时写入多个字段。

要使用 attributes= 方法,请在模型实例上调用该方法并传递包含要设立的字段和值的哈希对象。以下示例演示如何使用 attributes= 方法在 person文档上设立first_namemiddle_name 字段:

person.attributes = { first_name: "Jean-Baptiste", middle_name: "Emmanuel" }

要使用 write_attributes 方法,请在模型实例上调用该方法并传递要设立的字段和值。以下示例演示如何使用 write_attributes 方法在 person文档上设立first_namemiddle_name 字段:

person.write_attributes(
first_name: "Jean-Baptiste",
middle_name: "Emmanuel",
)

Mongoid支持以下更新操作符,您可以将这些操作符作为方法调用在模型实例上。这些方法以原子方式执行操作并跳过验证和回调。

下表描述了 Mongoid 支持的操作符:

Operator
说明
例子

add_to_set

将指定值添加到数组值字段。

person.add_to_set(aliases: "Bond")

bit

对字段执行按位更新。

person.bit(age: { and: 10, or: 12 })

inc

递增字段的值。

person.inc(age: 1)

pop

删除大量字段的第一个或最后一个元素。

person.pop(aliases: 1)

pull

从大量字段中删除与指定条件匹配的一个或多个值的所有实例。

person.pull(aliases: "Bond")

pull_all

从大量字段中删除指定值的所有实例。

person.pull_all(aliases: [ "Bond", "James" ])

push

将指定值附加到大量字段。

person.push(aliases: ["007","008"])

rename

重命名所有匹配文档中的字段。

person.rename(bday: :dob)

set

Updates an attribute on the model instance and, if the instance is already persisted, performs an atomic $set on the field, bypassing validations.
set can also deeply set values on Hash fields.
set can also deeply set values on embeds_one associations. If a model instance's embeds_one association document is nil, one is created before the update.
set cannot be used with has_one associations.
person = Person.create!(name: "Ricky Bobby")
# Updates `name` in the database
person.set(name: "Tyler Durden")

unset

删除所有匹配文档中的特定字段。

person.unset(:name)

要学习;了解有关更新操作符的更多信息,请参阅MongoDB Server手册中的更新操作符。

要将原子操作群组在一起,可以对模型实例使用 atomically 方法。 Mongoid 会在单个原子命令中发送您传递给 atomically区块的所有操作。

注意

使用事务以原子方式修改多个文档

原子操作一次应用于一个文档。因此,嵌套的atomically 块无法在一个原子操作中对多个文档进行更改。要在一个原子操作中更改多个文档,请使用多文档事务。要学习;了解有关事务的更多信息,请参阅 事务和会话指南。

以下示例展示如何使用 atomically 以原子方式更新文档中的多个字段:

person.atomically do
person.inc(age: 1)
person.set(name: 'Jake')
end

更新单个文档时,您可以嵌套 #atomically 块。默认情况下,Mongoid 在区块结束时执行每个区块定义的原子写入。以下示例展示了如何嵌套 atomically 区块:

person.atomically do
person.atomically do
person.inc(age: 1)
person.set(name: 'Jake')
end
raise 'An exception'
# Name and age changes are persisted
end

在前面的示例中,$inc$set 操作在内层 atomically区块的末尾执行。

atomically 方法接受 join_context: true 选项,以指定操作在最外层 atomically区块的末尾执行。启用此选项后,只有最外层的区块或 join_contextfalse 的第一个区块会将更改写入数据库。以下示例将 join_context 选项设置为 true

person.atomically do
person.atomically(join_context: true) do
person.inc(age: 1)
person.set(name: 'Jake')
end
raise 'An exception'
# Name and age changes are not persisted
end

在前面的示例中,Mongoid 在最外层 atomically区块的末尾执行 $inc$set 操作。但是,由于在区块结束之前引发异常并且这些操作可以运行,因此更改不会持久化。

您还可以全局启用上下文联接,以便默认下在最外层的atomically 区块中执行操作。要全局启用此选项,请在 文件中将join_contexts 设立选项设置为 。要学习;了解有关true mongoid.ymlMongoid 配置选项的更多信息,请参阅自管理配置文件选项。

当您将 join_contexts 全局设立为 true 时,您可以在 atomically区块上使用 join_context: false 选项,以便仅在该区块的区块末尾运行操作。

您可以使用类似于 Active Model 中提供的 Mongoid API追踪已更改(“脏”)字段。如果修改了模型中定义的字段,Mongoid 会将该模型标记为脏,并允许您执行特殊操作。以下部分描述了如何与脏模型交互。

Mongoid 记录从模型实例化(作为新文档或从数据库检索模型)到保存模型之间的更改。任何持久性操作都会清除更改。

Mongoid 创建特定于模型的方法,允许您探索对模型实例的更改。以下代码演示了查看模型实例更改的方法:

# Retrieves a person instance
person = Person.first
# Sets a new `name` value
person.name = "Sarah Frank"
# Checks to see if the document is changed
person.changed? # true
# Gets an array of changed fields.
person.changed # [ :name ]
# Gets a hash of the old and changed values for each field
person.changes # { "name" => [ "Sarah Frink", "Sarah Frank" ] }
# Checks if a specific field is changed
person.name_changed? # true
# Gets the changes for a specific field
person.name_change # [ "Sarah Frink", "Sarah Frank" ]
# Gets the previous value for a field
person.name_was # "Sarah Frink"

注意

跟踪关联变更

在文档上设置关联不会修改 changeschanged_attributes 哈希值。所有类型的关联都是如此。但是,更改引用关联上的 _id字段会导致更改显示在 changeschanged_attributes 哈希中。

您可以通过调用 reset 方法将已更改的字段重置为之前的值,如以下代码所示:

person = Person.first
person.name = "Sarah Frank"
# Reset the changed `name` field
person.reset_name!
person.name # "Sarah Frink"

Mongoid 使用脏跟踪作为所有持久性操作的基础。它会评估文档的更改,并仅自动更新已更改的内容,而其他框架则在每次保存时写入整个文档。如果您不进行任何更改,则当您调用 Model#save 时,Mongoid 将不会访问权限数据库。

将模型持久化到MongoDB后,Mongoid 会清除当前更改。不过,您仍然可以通过调用 previous_changes 方法查看之前所做的更改,如以下代码所示:

person = Person.first
person.name = "Sarah Frank"
person.save # Clears out current changes
# Lists the previous changes
person.previous_changes
# { "name" => [ "Sarah Frink", "Sarah Frank" ] }

Mongoid 目前存在一个问题,即无法将容器类型(例如 SetArray)的属性更改保存到MongoDB。您必须为所有字段(包括容器类型)指定值,以便保存到MongoDB中。

示例,按以下代码所示向Set 实例添加项目不会将更改持久保存到MongoDB:

person = Person.new
person.interests
# => #<Set: {}>
person.interests << 'Hiking'
# => #<Set: {"Hiking"}>
person.interests
# => #<Set: {}> # Change does not take effect

要保留此更改,您必须在模型外部修改字段值并将其分配回模型,如以下代码所示:

person = Person.new
interests = person.interests
# => #<Set: {}>
interests << 'Hiking'
# => #<Set: {"Hiking"}>
# Assigns the Set to the field
person.interests = interests
# => #<Set: {"Hiking"}>
person.interests
# => #<Set: {"Hiking"}>

您可以通过以下方式将文档标记为只读,具体取决于 Mongoid.legacy_readonly功能标志的值:

  • 如果此标志已关闭,则可以通过在文档上调用 readonly!方法将文档标记为只读。如果尝试执行任何持久性操作(包括但不限于保存、更新、删除和销毁),生成的只读文档会引发ReadonlyDocument 错误。请注意,重新加载不会重置只读状态。

    person = Person.first
    person.readonly? # => false
    person.readonly! # Sets the document as read-only
    person.readonly? # => true
    person.name = "Larissa Shay" # Changes the document
    person.save # => raises ReadonlyDocument error
    person.reload.readonly? # => true
  • 如果此标志已变为on ,则可以在项目文档后使用onlywithout 等方法将文档标记为只读。因此,您无法删除或销毁只读文档,因为 Mongoid 会引发ReadonlyDocument 错误,但您可以保存并更新它。如果重新加载文档,则会重置只读状态。

    person = Person.only(:name).first
    person.readonly? # => true
    person.destroy # => raises ReadonlyDocument error
    person.reload.readonly? # => false

    提示

    投射

    要学习;了解有关投影的更多信息,请参阅“修改查询结果”指南中的“返回指定字段”部分。

您还可以通过覆盖 readonly? 方法将文档设为只读,如以下代码所示:

class Person
include Mongoid::Document
field :name, type: String
def readonly?
true
end
end
person = Person.first
person.readonly? # => true
person.destroy # => raises ReadonlyDocument error

要学习;了解有关指定查询筛选器的更多信息,请参阅指定查询指南。

要学习;了解有关在模型上设置验证规则的更多信息,请参阅 文档验证指南。

要学习;了解有关定义回调的更多信息,请参阅 回调指南。

后退

与数据交互