Docs 菜单
Docs 主页
/ / /
Mongoid
/

字段定义

在此页面上

  • 字段类型
  • 非类型化字段
  • 字段类型:StringifiedSymbol
  • 字段类型:符号
  • 字段类型:哈希
  • 字段类型:时间
  • 字段类型:日期
  • 字段类型:日期时间
  • 字段类型:Regexp
  • 大十进制字段
  • 使用符号或字符串代替类
  • 指定字段默认值
  • 指定存储字段名称
  • 字段别名
  • 保留的名称
  • 字段重新定义
  • 自定义 ID
  • 不可转换值
  • 自定义字段行为
  • 自定义 Getter 和 Setter
  • 自定义字段类型
  • 自定义字段选项
  • 动态字段
  • 字段名中的特殊字符
  • 本地化字段
  • 本地化 :present 字段选项
  • 回退
  • 查询
  • 索引
  • 只读属性
  • 时间戳字段
  • 带有点/句点 (.) 和美元符号 ($) 的字段名称

MongoDB使用 BSON types存储底层文档数据,Mongoid 会在应用程序运行时将BSON types转换为Ruby类型。 例如,使用 type: :float定义的字段将在内存中使用 Ruby Float类,并将作为 BSON double类型保留在数据库中。

字段类型定义决定了 Mongoid 在构造查询和从数据库检索/写入字段时的行为方式。具体而言:

  1. 在运行时为字段赋值时,这些值将转换为指定的类型。

  2. 将数据持久保存到MongoDB时,数据会以适当的类型发送,从而允许在MongoDB中或通过其他工具进行更丰富的数据操作。

  3. 查询文档时,查询参数在发送到 MongoDB 之前会转换为指定类型。

  4. 从数据库检索文档时,字段值将转换为指定类型。

更改模型类中的字段定义不会更改已存储在 MongoDB 中的数据。要更新现有文档字段的类型或内容,必须将该字段重新保存到数据库中。请注意,由于 Mongoid 会追踪模型上哪些属性发生更改并仅保存更改的属性,因此在更改现有字段的类型而不更改存储的值时,可能需要显式写入字段值。

考虑使用一个简单的类来对应用程序中的人进行建模。一个人可能有名字、date_of_birth 和体重。我们可以使用 field 宏为人员定义这些属性。

class Person
include Mongoid::Document
field :name, type: String
field :date_of_birth, type: Date
field :weight, type: Float
end

有效的字段类型如下:

Mongoid 还将字符串 "Boolean" 识别为 Mongoid::Boolean 类的别名。

要定义自定义字段类型,请参阅下方的自定义字段类型

注意

不支持使用 BSON::Int64BSON::Int32 类型作为字段类型。将这些类型保存到数据库的操作将按预期运行,但是,查询它们将返回原生 Ruby Integer 类型。查询类型为 BSON::Decimal128 的字段将返回 BSON <=4 中类型为 BSON::Decimal128 的值,以及 BSON 5+ 中类型为 BigDecimal 的值。

不为字段指定类型与指定Object类型相同。 此类字段是非类型化的:

class Product
include Mongoid::Document
field :properties
# Equivalent to:
field :properties, type: Object
end

无类型字段可以存储任何可直接序列化为 BSON 的类型的值。 当字段可能包含不同类型的值(即,它是变体类型字段),或者事先未知值的类型时,这非常有用:

product = Product.new(properties: "color=white,size=large")
product.properties
# => "color=white,size=large"
product = Product.new(properties: {color: "white", size: "large"})
product.properties
# => {:color=>"white", :size=>"large"}

当为字段赋值时,Mongoid 仍然执行 mongoization,但使用值的类而不是字段类型来执行 mongoization 逻辑。

product = Product.new(properties: 0..10)
product.properties
# The range 0..10, mongoized:
# => {"min"=>0, "max"=>10}

从数据库读取数据时,Mongoid 不会对非类型化字段执行任何类型转换。 因此,尽管可以将任何 BSON 可序列化的值写入非类型化字段,但需要在数据库读取端进行特殊处理的值通常无法在非类型化字段中正常工作。 在 Mongoid 支持的字段类型中,以下类型的值不应存储在非类型化字段中:

  • Date (值将作为Time返回)

  • DateTime (值将作为Time返回)

  • Range (值将作为Hash返回)

StringifiedSymbol字段类型是推荐的字段类型,用于存储应作为符号向Ruby应用程序公开的值。 使用Symbol字段类型时,Mongoid 默认将值存储为BSON符号。 有关BSON符号类型的更多信息,请参阅此处。 但是, BSON符号类型已弃用,并且很难在没有原生符号类型的编程语言中使用,因此StringifiedSymbol类型允许使用符号,同时确保与其他驱动程序的互操作性。 StringifiedSymbol类型将数据库中的所有数据存储为字符串,同时将值作为符号公开给应用程序。

使用示例如下:

class Post
include Mongoid::Document
field :status, type: StringifiedSymbol
end
post = Post.new(status: :hello)
# status is stored as "hello" on the database, but returned as a Symbol
post.status
# => :hello
# String values can be assigned also:
post = Post.new(status: "hello")
# status is stored as "hello" on the database, but returned as a Symbol
post.status
# => :hello

所有非字符串值在发送到数据库时都将(通过 to_s)被字符串化,并且所有值在返回到应用程序时都将被转换为符号。无法直接转换为符号的值(例如整数和数组)将首先转换为字符串,再转换为符号,然后再返回到应用程序。

例如,将整数设置为 status

post = Post.new(status: 42)
post.status
# => :"42"

如果对包含 BSON 符号的字段应用了 StringifiedSymbol 类型,则在下次保存时,这些值将存储为字符串而不是 BSON 符号。这样,就能够执行从数据库中当前存储字符串或 BSON 符号的字段到 StringifiedSymbol 字段类型的透明惰性迁移。

新应用程序应使用StringifiedSymbol 字段类型在数据库中存储 Ruby 符号。 StringifiedSymbol字段类型提供与其他应用程序和编程语言的最大兼容性,并且在所有情况下都具有相同的行为。

Mongoid 还提供已弃用的 Symbol 字段类型,用于将 Ruby 符号序列化为 BSON 符号。由于 BSON 规范已弃用 BSON 符号类型,单独使用时,bson gem 会将 Ruby 符号序列化为 BSON 字符串。但是,为了保持与旧数据集的向后兼容性,mongo gem 会覆盖此行为,将 Ruby 符号序列化为 BSON 符号。对于指定查询包含 BSON 符号字段的文档,这是必要的。

要覆盖默认行为并配置mongo gem(以及 Mongoid)将符号值编码为字符串,请在您的项目中包含以下代码片段:

class Symbol
def bson_type
BSON::String::BSON_TYPE
end
end

使用哈希类型的字段时,请注意遵守 mongoDB 的合法键名,否则将无法正确存储值。

class Person
include Mongoid::Document
field :first_name
field :url, type: Hash
# will update the fields properly and save the values
def set_vals
self.first_name = 'Daniel'
self.url = {'home_page' => 'http://www.homepage.com'}
save
end
# all data will fail to save due to the illegal hash key
def set_vals_fail
self.first_name = 'Daniel'
self.url = {'home.page' => 'http://www.homepage.com'}
save
end
end

Time 字段存储值存储为配置的时区域中的Time实例。

DateDateTime 实例在赋值给 Time 字段时会转换为 Time 实例:

class Voter
include Mongoid::Document
field :registered_at, type: Time
end
Voter.new(registered_at: Date.today)
# => #<Voter _id: 5fdd80392c97a618f07ba344, registered_at: 2020-12-18 05:00:00 UTC>

在上方示例中,由于应用程序未配置为使用 UTC 时间,因此该值被解释为当地时间今天的开始时间。

注意

当数据库包含 Time 字段的字符串值时,Mongoid 使用 Time.parse 解析该字符串值,将没有时区的值按照当地时间加以对待。

Date 字段赋值时,Mongoid 允许使用多种类型的值:

  • Date - 提供的日期按原样存储。

  • TimeDateTimeActiveSupport::TimeWithZone - 值的日期组件采用值的时区日期。

  • String — 使用字符串中指定的日期。

  • IntegerFloat - 该值为 UTC 时间戳,会转换为配置的时区(请注意,Mongoid.use_utc 对此转换没有影响),然后从结果时间中获取日期。

换句话说,如果在值中指定了日期,则将使用该日期,而无需先将值转换为配置的时区。

由于日期和时间到日期的转换是有损的(它会丢弃时间部分),特别是当应用程序使用不同时区的时间运行时,建议将 StringTimeDateTime 对象显式转换为 Date 对象,然后对类型为 Date 的字段赋值。

注意

当数据库包含 Date 字段的字符串值时,Mongoid 使用 Time.parse 解析该字符串值,丢弃生成的 Time 对象的时间部分而使用日期部分。Time.parse 将没有时区的值按照当地时间加以对待。

MongoDB 将所有时间存储为 UTC 时间戳。向 DateTime 字段赋值或查询 DateTime 字段时,Mongoid 会将传入的值转换为 UTC Time,然后再将它发送到 MongoDB 服务器。

TimeActiveSupport::TimeWithZoneDateTime 对象嵌入了时区信息,并且持久保存的值是指定的时间点 (UTC)。检索该值时,返回的该值的时区由配置的时区设置定义。

class Ticket
include Mongoid::Document
field :opened_at, type: DateTime
end
Time.zone = 'Berlin'
ticket = Ticket.create!(opened_at: '2018-02-18 07:00:08 -0500')
ticket.opened_at
# => Sun, 18 Feb 2018 13:00:08 +0100
ticket
# => #<Ticket _id: 5c13d4b9026d7c4e7870bb2f, opened_at: 2018-02-18 12:00:08 UTC>
Time.zone = 'America/New_York'
ticket.opened_at
# => Sun, 18 Feb 2018 07:00:08 -0500
Mongoid.use_utc = true
ticket.opened_at
# => Sun, 18 Feb 2018 12:00:08 +0000

Mongoid 还支持将整数和浮点数转换为 DateTime。这样做时,整数/浮点数被假定为 Unix 时间戳(UTC 格式):

ticket.opened_at = 1544803974
ticket.opened_at
# => Fri, 14 Dec 2018 16:12:54 +0000

如果将一个字符串用作 DateTime 字段值,则对应行为将取决于该字符串是否包含时区。如果没有指定时区,则使用默认的 Mongoid 时区

Time.zone = 'America/New_York'
ticket.opened_at = 'Mar 4, 2018 10:00:00'
ticket.opened_at
# => Sun, 04 Mar 2018 15:00:00 +0000

如果指定时区,则应遵守:

ticket.opened_at = 'Mar 4, 2018 10:00:00 +01:00'
ticket.opened_at
# => Sun, 04 Mar 2018 09:00:00 +0000

注意

当数据库包含 DateTime 字段的字符串值时,Mongoid 使用 Time.parse 解析该字符串值,将没有时区的值按照当地时间加以对待。

MongoDB支持在文档中存储正则表达式,以及使用正则表达式进行查询。 请注意, MongoDB使用 与 Perl 兼容的正则表达式 (PCRE) Ruby使用 Onigmo ,它是 Oniguruma 正则表达式引擎 的一个分支 。这两个正则表达式实现通常提供相同的功能,但有几个重要的语法差异。

当字段声明为 Regexp 类型时,Mongoid 会将 Ruby 正则表达式转换为 BSON 正则表达式,并将结果存储在 MongoDB 中。从数据库中检索该字段会生成一个 BSON::Regexp::Raw 实例:

class Token
include Mongoid::Document
field :pattern, type: Regexp
end
token = Token.create!(pattern: /hello.world/m)
token.pattern
# => /hello.world/m
token.reload
token.pattern
# => #<BSON::Regexp::Raw:0x0000555f505e4a20 @pattern="hello.world", @options="ms">

BSON::Regexp::Raw 上使用 #compile 方法,返回 Ruby 正则表达式:

token.pattern.compile
# => /hello.world/m

请注意,如果正则表达式最初不是 Ruby 正则表达式,则对其调用 #compile 可能会生成不同的正则表达式。例如,下面是与以 “hello” 结尾的字符串匹配的 PCRE:

BSON::Regexp::Raw.new('hello$', 's')
# => #<BSON::Regexp::Raw:0x0000555f51441640 @pattern="hello$", @options="s">

编译此正则表达式,将生成 Ruby 正则表达式,该表达式除了匹配以“hello”结尾的字符串外,还匹配换行前包含“hello”的字符串:

BSON::Regexp::Raw.new('hello$', 's').compile =~ "hello\nworld"
# => 0

这是因为 PCRE 和 Ruby 正则表达式中 $ 的含义不同。

BigDecimal 字段类型用于存储精度更高的数字。

BigDecimal 字段类型在数据库中以两种不同的方式存储值,具体取决于 Mongoid.map_big_decimal_to_decimal128 全局配置选项的值。如果此标志设置为 false(默认值),则 BigDecimal 字段将以字符串形式存储,否则将以 BSON::Decimal128 存储。

BigDecimal 字段类型在与 BSON::Decimal128 相互转换时有一些限制:

  • BSON::Decimal128 的范围和精度有限,而 BigDecimal 在范围和精度方面没有限制。BSON::Decimal128 的最大值约为 10^6145,最小值约为 -10^6145,最大精度为 34 位。尝试存储不适合 BSON::Decimal128 的值时,建议将其存储为字符串而不是 BSON::Decimal128。您可以通过将 Mongoid.map_big_decimal_to_decimal128 设置为 false 来执行此操作。如果尝试存储不适合 BSON::Decimal128 的值,则将引发错误。

  • BSON::Decimal128 能够接受带符号的 NaN 值,而 BigDecimal 不能。使用 BigDecimal 字段类型从数据库检索带符号的 NaN 值时,NaN 将不带符号。

  • BSON::Decimal128 在数据库中存储时保留尾随零。但是,BigDecimal 不保留尾随零,因此使用 BigDecimal 字段类型检索 BSON::Decimal128 值,可能导致精度损失。

在没有类型的字段(即动态类型字段)中存储 BigDecimalMongoid.map_big_decimal_to_decimal128false 时,还有一个注意事项。在本例中,BigDecimal 存储为一个字符串,由于正在使用动态字段,所以使用 BigDecimal 查询该字段将找不到 BigDecimal 的字符串,因为该查询正在查找 BigDecimal。为了查询该字符串,必须首先将 BigDecimal 转换为带有 to_s 的字符串。请注意,当字段类型为 BigDecimal 时就不存在这个问题。

如果希望完全避免使用 BigDecimal,则可以将字段类型设置为 BSON::Decimal128。这将支持您跟踪尾随零和带符号的 NaN 值。

在 Mongoid 的未来主要版本中, Mongoid.map_big_decimal_to_decimal128全局配置选项将默认为true 。当此标志打开时,查询中的BigDecimal值将与已存储在数据库中的字符串不匹配;它们仅与数据库中的decimal128值匹配。如果您有由字符串支持的 BigDecimal 字段,您有三个选项:

  1. Mongoid.map_big_decimal_to_decimal128 全局配置选项可以设置为 false,并且您可以继续将 BigDecimal 值存储为字符串。请注意,您放弃了将 BigDecimal 值存储为 decimal128 的优势,例如能够根据字段的数值进行查询和聚合。

  2. Mongoid.map_big_decimal_to_decimal128 全局配置选项可以设置为 true,您可以将该字段的所有值从字符串转换为数据库中的 decimal128 值。您应该在将全局配置选项设置为 true 之前执行此转换。完成此操作的示例查询如下:

    db.bands.updateMany({
    "field": { "$exists": true }
    }, [
    {
    "$set": {
    "field": { "$toDecimal": "$field" }
    }
    }
    ])

    此查询会更新所有具有给定字段的文档,将该字段设置为其相应的 decimal128值。请注意,此查询仅适用于 MongoDB 4.2+。

  3. Mongoid.map_big_decimal_to_decimal128 全局配置选项可以设置为 true,并且您可以同时为该字段使用字符串和 decimal128 值。这样,今后只有 decimal128 值会被插入并更新到数据库中。请注意,您仍然无法充分享受使用 decimal128 值的优势,但您的数据集正在慢慢迁移到所有 decimal128 值,因为旧字符串值已更新为 decimal128,并且添加了新的 decimal128 值。在这种设置下,您仍然可以按如下方式查询 BigDecimal 值:

    Mongoid.map_big_decimal_to_decimal128 = true
    big_decimal = BigDecimal('2E9')
    Band.in(sales: [big_decimal, big_decimal.to_s]).to_a

    该查询将查找所有满足如下条件的值:decimal128 值或与该值匹配的字符串。

Mongoid 允许使用符号或字符串而不是类来指定字段类型,例如:

class Order
include Mongoid::Document
field :state, type: :integer
# Equivalent to:
field :state, type: "integer"
# Equivalent to:
field :state, type: Integer
end

只有下列标准字段类型可以使用符号或字符串以这种方式指定。Mongoid 可识别以下扩展:

  • :array => Array

  • :big_decimal => BigDecimal

  • :binary => BSON::Binary

  • :boolean => Mongoid::Boolean

  • :date => Date

  • :date_time => DateTime

  • :float => Float

  • :hash => Hash

  • :integer => Integer

  • :object_id => BSON::ObjectId

  • :range => Range

  • :regexp => Regexp

  • :set => Set

  • :string => String

  • :stringified_symbol => StringifiedSymbol

  • :symbol => Symbol

  • :time => Time

可将字段配置为默认值。 默认值可以固定,如以下示例所示:

class Order
include Mongoid::Document
field :state, type: String, default: 'created'
end

默认值也可以指定为 Proc

class Order
include Mongoid::Document
field :fulfill_by, type: Time, default: ->{ Time.now + 3.days }
end

注意

不是 Proc 实例的默认值会在类加载时进行评估,这意味着以下两个定义并不等效:

field :submitted_at, type: Time, default: Time.now
field :submitted_at, type: Time, default: ->{ Time.now }

第二个定义很可能是所需的定义,它会导致提交时间设置为文档实例化时的当前时间。

要设置取决于文档状态的默认值,请在 Proc 实例内使用self,该实例将计算出正在操作的文档实例:

field :fulfill_by, type: Time, default: ->{
# Order should be fulfilled in 2 business hours.
if (7..8).include?(self.submitted_at.hour)
self.submitted_at + 4.hours
elsif (9..3).include?(self.submitted_at.hour)
self.submitted_at + 2.hours
else
(self.submitted_at + 1.day).change(hour: 11)
end
}

将默认值定义为 Proc 时,Mongoid 将在设置所有其他属性并初始化关联后应用默认值。要在设置其他属性之前应用默认值,请使用 pre_processed: true 字段选项:

field :fulfill_by, type: Time, default: ->{ Time.now + 3.days },
pre_processed: true

通过 Proc_id 字段指定自定义默认值时,pre_processed: true 选项也是必需的,确保通过关联正确设置 _id

field :_id, type: String, default: -> { 'hello' }, pre_processed: true

无模式数据库的一个缺点是 MongoDB 必须将所有字段信息与每份文档一起存储,因而会占用 RAM 和磁盘中的大量存储空间。限制这个问题的一种常见模式是给字段取一个较短的别名,同时保持应用程序中域的表现力。Mongoid 允许您这样做,并在为您执行转换时通过获取器、设置器和条件中的长名称引用域中的字段。

class Band
include Mongoid::Document
field :n, as: :name, type: String
end
band = Band.new(name: "Placebo")
band.attributes # { "n" => "Placebo" }
criteria = Band.where(name: "Placebo")
criteria.selector # { "n" => "Placebo" }

可以定义字段别名。该值将存储在目标字段中,但可以从目标字段或别名字段访问:

class Band
include Mongoid::Document
field :name, type: String
alias_attribute :n, :name
end
band = Band.new(n: 'Astral Projection')
# => #<Band _id: 5fc1c1ee2c97a64accbeb5e1, name: "Astral Projection">
band.attributes
# => {"_id"=>BSON::ObjectId('5fc1c1ee2c97a64accbeb5e1'), "name"=>"Astral Projection"}
band.n
# => "Astral Projection"

可以使用 unalias_attribute方法从模型类中删除别名。

class Band
unalias_attribute :n
end

unalias_attribute 可用于删除预定义的 id 别名。这对于在 id_id 字段中存储不同的值非常有用:

class Band
include Mongoid::Document
unalias_attribute :id
field :id, type: String
end
Band.new(id: '42')
# => #<Band _id: 5fc1c3f42c97a6590684046c, id: "42">

尝试在文档中定义与 Mongoid 中保留的方法名称冲突的字段将引发错误。可通过调用 Mongoid.destructive_fields 方法获取保留名称的列表。

默认情况下,Mongoid 允许在模型上重新定义字段。要在重新定义字段时引发错误,则将 duplicate_fields_exception 配置选项设置为 true

如果将该选项设置为“true”,以下示例将引发错误:

class Person
include Mongoid::Document
field :name
field :name, type: String
end

若仍要定义该字段,请使用 overwrite: true 选项:

class Person
include Mongoid::Document
field :name
field :name, type: String, overwrite: true
end

默认情况下,Mongoid 会定义针对文档的 _id 字段以包含由 Mongoid 自动生成的 BSON::ObjectId 值。

可通过替换 _id 字段定义来更改 _id 值的类型或设置其他默认值:

class Band
include Mongoid::Document
field :name, type: String
field :_id, type: String, default: ->{ name }
end

可以完全省略默认值:

class Band
include Mongoid::Document
field :_id, type: String
end

如果省略了 _id 的默认值,且应用程序没有提供 _id 值,Mongoid 将持久保存没有 _id 值的文档。在这种情况下,如果文档是顶层文档,服务器将分配 _id 值;如果文档是嵌入文档,则不会分配 _id 值。如果分配了值,Mongoid 不会在持久保存文档时自动检索该值,您必须采用其他方式获取持久保存的值(以及持久保存的完整文档):

band = Band.create!
=> #<Band _id: , >
band.id
=> nil
band.reload
# raises Mongoid::Errors::DocumentNotFound
Band.last
=> #<Band _id: 5fc681c22c97a6791f324b99, >

忽略 _id 字段在嵌入式文档中更为常见。

Mongoid 还定义了与_id别名的id字段。 如果需要,可以删除id别名(例如与使用id字段存储不同于_id的值的系统集成)。

在 Mongoid 8 中,Mongoid 标准化“不可转换”值的分配和读取处理。当一个值不能被强制转换为其字段的类型时,该值被认为“不可转换”。例如,数组将是整数字段的“不可转换”值。

不可转换值的分配已标准化为默认分配 nil。考虑以下示例:

class User
include Mongoid::Document
field :name, type: Integer
end
User.new(name: [ "hello" ])

将数组分配给整数类型的字段不起作用,因为数组不能强制为转换为整数。将不可转换的值分配给字段将导致写入 nil

user = User.new(name: [ "Mike", "Trout" ])
# => #<User _id: 62b222d43282a47bf73e3264, name: nil>

请注意,原始的不可转换值将与其字段名称一起存储在 attributes_before_type_cast 哈希中:

user.attributes_before_type_cast["name"]
# => ["Mike", "Trout"]

注意

请注意,对于数字字段,为整数字段定义的 to_i、为 Floats 定义的 to_f、为 BigDecimals 定义的 to_d 任何类都是可转换的。字符串是例外。如果字符串为数字,则仅调用相应的 to_* 方法。如果一个类仅定义 to_i 而没有定义 to_f 并且被分配给 Float 字段,则这是不可转换的,并且 Mongoid 将不会执行两步转换(即 to_i,然后to_f)。

当数据库中的文档包含的值的类型与其在 Mongoid 中的表示形式不同时,如果 Mongoid 无法将其强制转换为正确的类型,则会用nil替换该值。 考虑以下数据库中的模型和文档:

class User
include Mongoid::Document
field :name, type: Integer
end
{ _id: ..., name: [ "Mike", "Trout" ] }

从数据库读取此文档将导致模型的名称字段包含 nil

User.first.name
# => nil

数组类型的数据库值无法存储在属性中,因为数组无法强制转换为整数。请注意,原始的不可转换值将与其字段名称一起存储在 attributes_before_type_cast 哈希中:

user.attributes_before_type_cast["name"]
# => ["Mike", "Trout"]

注意

容器对象demongoize即 哈希、数组)尚未更改为允许自动持久化已更改的容器属性。 请参阅 MONGOID-2951 有关此主题的更长时间的讨论。

Mongoid 提供多种用于自定义字段行为的方法。

您可以覆盖字段的读取与赋值方法,以便在访问或写入这些字段时修改值。这些读取与赋值方法使用与字段相同的名称。使用读取与赋值方法内的 read_attributewrite_attribute 方法对原始属性值进行操作。

例如,Mongoid 提供 :default 字段选项以将默认值写入该字段。如果您希望在应用程序中拥有字段默认值但不希望保留它,则可以按如下方式重写 getter:

class DistanceMeasurement
include Mongoid::Document
field :value, type: Float
field :unit, type: String
def unit
read_attribute(:unit) || "m"
end
def to_s
"#{value} #{unit}"
end
end
measurement = DistanceMeasurement.new(value: 2)
measurement.to_s
# => "2.0 m"
measurement.attributes
# => {"_id"=>BSON::ObjectId('613fa0b0a15d5d61502f3447'), "value"=>2.0}

再例如,可将空字符串转换为 nil 值的字段可按如下方法实现:

class DistanceMeasurement
include Mongoid::Document
field :value, type: Float
field :unit, type: String
def unit=(value)
if value.blank?
value = nil
end
write_attribute(:unit, value)
end
end
measurement = DistanceMeasurement.new(value: 2, unit: "")
measurement.attributes
# => {"_id"=>BSON::ObjectId('613fa15aa15d5d617216104c'), "value"=>2.0, "unit"=>nil}

您可在 Mongoid 中定义自定义类型,并确定它们的序列化与反序列化方式。在此示例中,我们定义了一个新的字段类型 Point,并可将其用于模型类,如下所示:

class Profile
include Mongoid::Document
field :location, type: Point
end

然后,创建一个 Ruby 类来表示该类型。该类必须定义用于 MongoDB 序列化和反序列化的方法,如下所示:

class Point
attr_reader :x, :y
def initialize(x, y)
@x, @y = x, y
end
# Converts an object of this instance into a database friendly value.
# In this example, we store the values in the database as array.
def mongoize
[ x, y ]
end
class << self
# Takes any possible object and converts it to how it would be
# stored in the database.
def mongoize(object)
case object
when Point then object.mongoize
when Hash then Point.new(object[:x], object[:y]).mongoize
else object
end
end
# Get the object as it was stored in the database, and instantiate
# this custom class from it.
def demongoize(object)
Point.new(object[0], object[1])
end
# Converts the object that was supplied to a criteria and converts it
# into a query-friendly form.
def evolve(object)
case object
when Point then object.mongoize
else object
end
end
end
end

实例方法mongoize获取自定义类型对象的实例,并将其转换为将如何存储在数据库中的表示形式,即传递给 MongoDB Ruby 驱动程序。 在上面的示例中,我们希望将Point对象存储为Array 3} 形式的[ x, y ]

类方法 mongoize 与实例方法类似,但它必须将所有可能类型的对象作为输入进行处理。调用自定义类型字段的 setter 方法时,将使用 mongoize方法。

point = Point.new(12, 24)
venue = Venue.new(location: point) # This uses the Point#mongoize instance method.
venue = Venue.new(location: [ 12, 24 ]) # This uses the Point.mongoize class method.

类方法 demongoize 执行与 mongoize 相反的操作。它从 MongoDB Ruby 驱动程序获取原始对象并将其转换为自定义类型的实例。在这种情况下,数据库驱动程序会返回 Array,并且我们从中实例化 Point。调用自定义类型字段的 getter 时,会使用 demongoize 方法。请注意,在上面的示例中,由于 demongoize 调用 Point.new ,因此每次调用 getter 时都会生成 Point 的新实例。

Mongoid 始终会对从数据库检索到的值调用 demongoize 方法,但理论上,应用程序可使用任意输入来调用 demongoize。建议应用程序在其 demongoize 方法中添加对任意输入的处理。我们可按如下方式覆盖 Point 的 demogoize 方法:

def demongoize(object)
if object.is_a?(Array) && object.length == 2
Point.new(object[0], object[1])
end
end

请注意,仅当给定长度为2的数组时, demongoize才会创建新的Point ,否则返回nilmongoizedemongoize方法都应准备好接收任意输入,并且应对无法转换为自定义类型的值返回nil 。 有关更多详细信息,请参阅有关不可转换值的部分。

最后,类方法 evolvemongoize 类似,但在转换对象以便用于 Mongoid 查询条件时使用。

point = Point.new(12, 24)
Venue.where(location: point) # This uses Point.evolve

evolve方法还应准备好接收任意输入,但与mongoizedemongoize方法不同,它应对无法转换为自定义类型的值返回输入的值。 有关更多详细信息,请参阅有关不可转换值的部分。

当用户可见的属性值类型与声明的字段类型不同时,自定义字段类型可执行从用户可见的属性值到数据库中存储值的转换。例如,此操作可用于实现从一个枚举到另一个枚举的映射,使应用程序中的值描述更详细,数据库中存储的值更紧凑:

class ColorMapping
MAPPING = {
'black' => 0,
'white' => 1,
}.freeze
INVERSE_MAPPING = MAPPING.invert.freeze
class << self
# Takes application-scope value and converts it to how it would be
# stored in the database. Converts invalid values to nil.
def mongoize(object)
MAPPING[object]
end
# Get the value as it was stored in the database, and convert to
# application-scope value. Converts invalid values to nil.
def demongoize(object)
INVERSE_MAPPING[object]
end
# Converts the object that was supplied to a criteria and converts it
# into a query-friendly form. Returns invalid values as is.
def evolve(object)
MAPPING.fetch(object, object)
end
end
end
class Profile
include Mongoid::Document
field :color, type: ColorMapping
end
profile = Profile.new(color: 'white')
profile.color
# => "white"
# Writes 0 to color field
profile.save!

您可以为 field 宏函数定义自定义选项,以便在加载模型类时扩展其行为。

例如,我们将定义一个:max_length选项,为字段添加长度验证器。 首先,在初始化程序中声明新的字段选项,将其处理程序函数指定为区块:

# in /config/initializers/mongoid_custom_fields.rb
Mongoid::Fields.option :max_length do |model, field, value|
model.validates_length_of field.name, maximum: value
end

然后,将其用于您的模型类:

class Person
include Mongoid::Document
field :name, type: String, max_length: 10
end

请注意,每当在字段定义中使用该选项时,即使该选项的值为 false 或 nil,也会调用处理程序函数。

默认情况下,Mongoid 要求使用 field 声明显式定义可对文档设置的所有字段。Mongoid 还支持从存储在数据库中的任意哈希或文档动态地创建字段。当模型使用未显式定义的字段时,此类字段则称为动态字段

如需启用动态字段,请在模型中包含 Mongoid::Attributes::Dynamic 模块:

class Person
include Mongoid::Document
include Mongoid::Attributes::Dynamic
end
bob = Person.new(name: 'Bob', age: 42)
bob.name
# => "Bob"

可以在同一模型类中使用 field 声明和动态字段。具有 field 声明的属性将根据 field 声明进行处理,其余属性将视为动态字段。

动态字段中的属性值最初必须通过向构造函数传递属性哈希、通过attributes=进行批量分配、通过[]=进行批量分配、使用write_attribute来制定,或者必须已存在于数据库中。

# OK
bob = Person.new(name: 'Bob')
# OK
bob = Person.new
bob.attributes = {age: 42}
# OK
bob = Person.new
bob['age'] = 42
# Raises NoMethodError: undefined method age=
bob = Person.new
bob.age = 42
# OK
bob = Person.new
# OK - string access
bob.write_attribute('age', 42)
# OK - symbol access
bob.write_attribute(:name, 'Bob')
# OK, initializes attributes from whatever is in the database
bob = Person.find('123')

如果特定模型实例的属性哈希中不存在某个属性,则相应字段的读取者和写入者均未定义,并且调用它们会引发 NoMethodError

bob = Person.new
bob.attributes = {age: 42}
bob.age
# => 42
# raises NoMethodError
bob.name
# raises NoMethodError
bob.name = 'Bob'
# OK
bob['name'] = 'Bob'
bob.name
# => "Bob"

属性总是可以使用大量属性访问或 read_attribute 来读取(这也适用于不使用动态字段的模型):

bob = Person.new(age: 42)
# OK - string access
bob['name']
# => nil
# OK - symbol access
bob[:name]
# => nil
# OK - string access
bob['age']
# => 42
# OK - symbol access
bob[:age]
# => 42
# OK
bob.attributes['name']
# => nil
# OK
bob.attributes['age']
# => 42
# Returns nil - keys are always strings
bob.attributes[:age]
# => nil
# OK
bob.read_attribute('name')
# => nil
# OK
bob.read_attribute(:name)
# => nil
# OK - string access
bob.read_attribute('age')
# => 42
# OK - symbol access
bob.read_attribute(:age)
# => 42

注意

read_attribute 方法返回的值和存储在 attributes 哈希中的值就是 mongoized 值。

Mongoid 允许动态字段名称包含空格和标点符号:

bob = Person.new('hello world' => 'MDB')
bob.send('hello world')
# => "MDB"
bob.write_attribute("hello%world", 'MDB')
bob[:"hello%world"]
# => "MDB"

Mongoid 通过 I18 n gem 支持本地化字段 。

class Product
include Mongoid::Document
field :description, type: String, localize: true
end

通过将该字段告知 localize,Mongoid 会在幕后将该字段存储为区域设置/值对的哈希值,但对其的正常访问将表现为字符串。

I18n.default_locale = :en
product = Product.new
product.description = "Marvelous!"
I18n.locale = :de
product.description = "Fantastisch!"
product.attributes
# { "description" => { "en" => "Marvelous!", "de" => "Fantastisch!" }

您可以使用相应的 _translations 方法一次性获取和设置所有翻译。

product.description_translations
# { "en" => "Marvelous!", "de" => "Fantastisch!" }
product.description_translations =
{ "en" => "Marvelous!", "de" => "Wunderbar!" }

本地化字段可与任一字段类型一起使用。例如,它们可与浮点字段一起使用,以表示与货币的差异:

class Product
include Mongoid::Document
field :price, type: Float, localize: true
field :currency, type: String, localize: true
end

通过以这种方式创建模型,我们可以将价格与货币类型分开,这样在查询或聚合该字段时,就可以使用价格上所有与数字相关的功能(前提是您必须在存储的翻译哈希值中建立索引)。我们可以如下创建该模型的实例:

product = Product.new
I18n.locale = :en
product.price = 1.00
product.currency = "$"
I18n.locale = :he
product.price = 3.24
product.currency = "₪"
product.attributes
# => { "price" => { "en" => 1.0, "he" => 3.24 }, "currency" => { "en" => "$", "he" => "₪" } }

创建本地化字段时,Mongoid 支持 :present 选项:

class Product
include Mongoid::Document
field :description, localize: :present
end

此选项会自动删除 blank 值(即,为 blank? 方法返回 true 的那些值),而这些值位于 _translations 哈希中:

I18n.default_locale = :en
product = Product.new
product.description = "Marvelous!"
I18n.locale = :de
product.description = "Fantastisch!"
product.description_translations
# { "en" => "Marvelous!", "de" => "Fantastisch!" }
product.description = ""
product.description_translations
# { "en" => "Marvelous!" }

当为 :de 区域设置写入空字符串时,将从 _translations 哈希中删除 "de" 键,而不是写入空字符串。

Mongoid 与 18n 回退 集成 。要使用回退,必须显式启用相应的功能。

在 Rails 应用程序中,在您的环境中将 config.i18n.fallbacks 配置设置设为 true 并指定回退语言:

config.i18n.fallbacks = true
config.after_initialize do
I18n.fallbacks[:de] = [ :en, :es ]
end

在非 Rails 应用程序中,将回退模块包含到正在使用的 I18n 后端,并指定回退语言:

require "i18n/backend/fallbacks"
I18n::Backend::Simple.send(:include, I18n::Backend::Fallbacks)
I18n.fallbacks[:de] = [ :en, :es ]

启用回退功能后,如果当前语言中不存在翻译,则将在回退语言查找翻译:

product = Product.new
I18n.locale = :en
product.description = "Marvelous!"
I18n.locale = :de
product.description # "Marvelous!"

Mongoid 还定义了字段的 :fallbacks 选项,用于禁用特定字段的回退功能:

class Product
include Mongoid::Document
field :description, type: String, localize: true, fallbacks: false
end
product = Product.new
I18n.locale = :en
product.description = "Marvelous!"
I18n.locale = :de
product.description # nil

请注意,此选项默认为 true

注意

在18 和1.1 中,回退的行为已 更改 始终要求提供回退区域设置的显式列表,而不是在未提供回退区域设置时回退到默认区域设置。

查询是否存在使用 Mongoid 条件 API 的本地化字段时,Mongoid 将自动更改条件以匹配当前的区域设置。

# Match all products with Marvelous as the description. Locale is en.
Product.where(description: "Marvelous!")
# The resulting MongoDB query filter: { "description.en" : "Marvelous!" }

如果计划对本地化字段进行广泛查询,应该为要计划搜索的每个语言区域建立索引。

class Product
include Mongoid::Document
field :description, localize: true
index "description.de" => 1
index "description.en" => 1
end

您可以告诉 Mongoid 某些属性为只读。 这将允许使用这些属性创建文档,但在使用批量更新方法(例如update_attributes)时将忽略相关更改:

class Band
include Mongoid::Document
field :name, type: String
field :origin, type: String
attr_readonly :name, :origin
end
band = Band.create(name: "Placebo")
band.update_attributes(name: "Tool") # Filters out the name change.

如果您明确尝试单独更新或删除只读属性,则会引发 ReadonlyAttribute 异常:

band.update_attribute(:name, "Tool") # Raises the error.
band.remove_attribute(:name) # Raises the error.

使用其 setter 对只读属性的分配将被忽略:

b = Band.create!(name: "The Rolling Stones")
# => #<Band _id: 6287a3d5d1327a5292535383, name: "The Rolling Stones", origin: nil>
b.name = "The Smashing Pumpkins"
# => "The Smashing Pumpkins"
b.name
# => "The Rolling Stones"

对原子持久性运算符(如 bitinc)的调用将保留对只读字段的更改。

Mongoid 在Mongoid::Timestamps中提供了一个时间戳模块,可以将其包含在内以获得created_atupdated_at字段的基本行为。

class Person
include Mongoid::Document
include Mongoid::Timestamps
end

您也可选择仅将特定时间戳用于创建或修改。

class Person
include Mongoid::Document
include Mongoid::Timestamps::Created
end
class Post
include Mongoid::Document
include Mongoid::Timestamps::Updated
end

如果要关闭特定调用的时间戳,请使用无时方法:

person.timeless.save
Person.timeless.create!

如果您希望使用带有别名的较短时间戳字段来节省空间,则可以包含模块的简短版本。

class Band
include Mongoid::Document
include Mongoid::Timestamps::Short # For c_at and u_at.
end
class Band
include Mongoid::Document
include Mongoid::Timestamps::Created::Short # For c_at only.
end
class Band
include Mongoid::Document
include Mongoid::Timestamps::Updated::Short # For u_at only.
end

不建议在字段名称中使用点/句点 (.) 以及以美元符号 ($) 开头的字段名称,因为 Mongoid 对检索和操作存储在这些字段中的文档提供的支持有限。

Mongoid 和MongoDB查询语言(MQL ) 通常使用点/句点字符 ( . ) 来分隔遍历嵌入式文档的字段路径(Field Path)中的字段名称,以及作为运算符的以美元符号 ( $ ) 开头的单词。 MongoDB为实现与其他软件的互操作性而对使用包含点并以美元符号开头的字段名称提供有限支持,但由于此支持仅限于特定操作符(例如 getFieldsetField ),并且要求在查询和更新中使用聚合管道,应用程序应避免在字段名称字段使用点,并尽可能避免以美元符号开头。

从 Mongoid 版本 8 开始,现在允许用户访问以美元符号开头且包含点的字段。可以使用 send 方法进行访问,如下所示:

class User
include Mongoid::Document
field :"first.last", type: String
field :"$_amount", type: Integer
end
user = User.first
user.send(:"first.last")
# => Mike.Trout
user.send(:"$_amount")
# => 42650000

也可以使用 read_attribute 访问这些字段:

user.read_attribute("first.last")
# => Mike.Trout

由于服务器限制,更新和替换包含点与美元符号的字段需使用特殊运算符。因此,禁止对这些字段调用读取方法,否则会引发以下错误:

class User
include Mongoid::Document
field :"first.last", type: String
field :"$_amount", type: Integer
end
user = User.new
user.send(:"first.last=", "Shohei.Ohtani")
# raises a InvalidDotDollarAssignment error
user.send(:"$_amount=", 8500000)
# raises a InvalidDotDollarAssignment error

后退

模式配置