执行数据操作
在此页面上
Overview
在本指南中,您可以学习;了解如何使用 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]
save!
使用 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!
方法更新现有模型实例的属性。 如果遇到任何验证或服务器错误,此方法会引发异常。
以下示例展示如何使用 update_attributes!
更新现有文档的 first_name
和 last_name
属性:
person.update_attributes!( first_name: "Maximilian", last_name: "Hjalmar" )
update_attributes
update_attributes
方法不会在验证错误时引发异常。 如果该方法通过验证且文档已更新,则返回 true
,否则返回 false
。
以下示例展示了如何使用 update_attributes
:
person.update_attributes( first_name: "Hasan", last_name: "Emine" )
update_attribute
您可以使用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
您可以使用 touch
方法将文档的 updated_at
时间戳更新为当前时间。 touch
将更新级联到文档的任何 belongs_to
关联。 您还可以传递另一个具有时间值的字段作为选项,以同时更新该字段。
以下示例使用 touch
更新updated_at
和 audited_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
delete_all
方法会删除集合中由 Mongoid 模型类建模的所有文档。 delete_all
不运行回调。
以下示例展示了如何使用 delete_all
删除所有 Person
文档:
Person.delete_all
destroy_all
destroy_all
方法会删除集合中由 Mongoid 模型类建模的所有文档。 这可能是一项成本高昂的操作,因为 Mongoid 会将所有文档加载到内存中。
以下示例展示了如何使用 destroy_all
删除所有 Person
文档:
Person.destroy_all
持久性属性
以下部分介绍了 Mongoid 提供的属性,您可以使用这些属性来检查文档是否已持久保存到数据库中。
new_record?
如果模型实例尚未保存到数据库,则 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_attribute 和 write_attribute
您可以使用 read_attribute
和 write_attribute
方法指定读取或写入字段时的自定义行为。 您可以在定义模型时使用这些方法,也可以在模型实例上调用这些方法。
要使用 read_attribute
获取字段,请将该字段的名称传递给该方法。 要使用 write_attribute
设立字段,请传递字段名称和要分配的值。
以下示例在模型定义中使用 read_attribute
和 write_attribute
将 first_name
和 first_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_attribute
和 write_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_name
和 middle_name
字段:
person.attributes = { first_name: "Jean-Baptiste", middle_name: "Emmanuel" }
要使用 write_attributes
方法,请在模型实例上调用该方法并传递要设立的字段和值。 以下示例演示如何使用 write_attributes
方法在 person
文档上设立first_name
和 middle_name
字段:
person.write_attributes( first_name: "Jean-Baptiste", middle_name: "Emmanuel", )
原子更新操作符
Mongoid支持以下更新操作符,您可以将这些操作符作为方法调用在模型实例上。 这些方法以原子方式执行操作并跳过验证和回调。
下表描述了 Mongoid 支持的操作符:
Operator | 说明 | 例子 | |||
---|---|---|---|---|---|
| 将指定值添加到数组值字段。 |
| |||
| 对字段执行按位更新。 |
| |||
| 递增字段的值。 |
| |||
| 删除大量字段的第一个或最后一个元素。 |
| |||
| 从大量字段中删除与指定条件匹配的一个或多个值的所有实例。 |
| |||
| 从大量字段中删除指定值的所有实例。 |
| |||
| 将指定值附加到大量字段。 |
| |||
| 重命名所有匹配文档中的字段。 |
| |||
| 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. |
| |||
| 删除所有匹配文档中的特定字段。 |
|
要学习;了解有关更新操作符的更多信息,请参阅MongoDB Server手册中的更新操作符。
对原子操作进行分组
要将原子操作群组在一起,可以对模型实例使用 atomically
方法。 Mongoid 会在单个原子命令中发送您传递给 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_context
为 false
的第一个区块会将更改写入数据库。 以下示例将 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
区块中执行操作。 要全局启用此选项,请在 mongoid.yml
文件中将 join_contexts
设立选项设置为 true
。 要学习;了解有关 Mongoid 配置选项的更多信息,请参阅自管理配置文件选项。
当您将 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"
注意
跟踪关联变更
在文档上设置关联不会修改 changes
或 changed_attributes
哈希值。 所有类型的关联都是如此。 但是,更改引用关联上的 _id
字段会导致更改显示在 changes
和 changed_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 目前存在一个问题,即无法将容器类型(例如 Set
或 Array
)的属性更改保存到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
,则可以在项目文档后使用only
或without
等方法将文档标记为只读。 因此,您无法删除或销毁只读文档,因为 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
更多信息
要学习;了解有关指定查询筛选器的更多信息,请参阅指定文档查询指南。
要学习;了解有关在模型上设置验证规则的更多信息,请参阅 文档验证指南。
要学习;了解有关定义回调的更多信息,请参阅“为数据模型自定义回调”指南。