Overview
在本指南中,您可以学习;了解Mongoid 支持的字段类型,您可以使用这些字段类型来定义MongoDB文档的模式。
MongoDB使用 BSON 类型来表示存储在文档字段中的数据类型。要在 Mongoid应用程序中使用BSON数据,Mongoid 必须在运行时将BSON 类型转换为Ruby类型。 示例,从数据库检索文档时,Mongoid 会将BSON double 类型转换为使用Ruby Float 类型。 当您再次保存文档时,Mongoid 会将字段转换回BSON double。
To learn more about modeling documents in Mongoid, see the Use the Document Module in Your Model guide.
注意
修改模型类中的字段定义不会更改数据库中存储的任何数据。 要更改数据库中字段的数据类型,必须再次重新保存数据。
字段类型
您可以使用 field 和 type 宏在模型类中定义字段名称和类型。 以下示例定义了 Person 类的字段:
class Person include Mongoid::Document field :name, type: String field :date_of_birth, type: Date field :weight, type: Float end
以下列表提供了可以在 Mongoid 中使用的字段类型:
ArrayBson::BinaryBigDecimalMongoid::BooleanorBooleanDateDateTimeFloatHashIntegerObjectBson::ObjectIdRangeRegexpSetStringMongoid::StringifiedSymbolTimeActiveSupport::TimeWithZone
注意
Mongoid 不支持BSON::Int64 或 BSON::Int32 作为字段类型。 Mongoid 会将这些值正确保存到数据库,但当您检索文档时,这些字段会以 Integer 类型返回。
同样,在查询 BSON::Decimal128 类型的字段时,Mongoid 以 BigDecimal 类型返回字段。
非类型化字段
如果您没有为字段指定类型,Mongoid 会将其解释为默认的Object 类型。 无类型字段可以存储可直接序列化为BSON的任何类型的值。 如果字段可能包含不同类型的数据,或者字段值的类型未知,则可以将该字段留为非类型化。
以下示例定义了一个具有非类型化字段的 Product 类:
class Product include Mongoid::Document field :name, type: String field :properties end
properties字段的类型为 Object,但会根据该字段中存储的数据类型而有所变化。 以下示例通过两种不同方式将数据保存到 properties字段:
product = Product.new(properties: "color=white,size=large") # properties field saved as String: "color=white,size=large" product = Product.new(properties: {color: "white", size: "large"}) # properties field saved as Object: {:color=>"white", :size=>"large"}
由于 Mongoid 在读取数据库时不会对非类型化字段执行任何类型转换,因此可能无法将需要特殊处理的值作为非类型化字段的值正确检索。 请勿在非类型化字段中存储以下BSON数据类型:
Date:在非类型化字段中返回为TimeDateTime:在非类型化字段中返回为TimeRange:在非类型化字段中返回为Hash
哈希
您可以使用 Hash 类型将 Hash 数据存储在字段中。 当您将字段指定为Hash 时,请确保遵循MongoDB命名限制,以确保这些值正确存储在数据库中。
以下示例创建一个 Person 类并将 url字段指定为 Hash。
class Person include Mongoid::Document field :first_name field :url, type: Hash end person = Person.new(url: {'home_page' => 'http://www.homepage.com'})
时间
您可以使用 Time字段值将值存储为BSON Time 实例。 Time 字段存储在为应用程序配置的时区域中。 要学习;了解有关配置时区的更多信息,请参阅应用程序配置指南的时区配置部分。
以下示例创建一个 Voter 类并指定 registered_at字段的值是 Time 类型:
class Voter include Mongoid::Document field :registered_at, type: Time end Voter.new(registered_at: Date.today)
注意
在指定为 Time 的字段中存储 Date 或 DateTime 值会在赋值时将该值转换为 Time。 如果您将字符串存储在 Time字段中,Mongoid 将使用 Time.parse 方法解析该字符串。 要进一步学习;了解Mongoid 如何转换查询,请参阅 指定查询指南的 字段类型查询转换 部分。
Date
您可以在指定为 Date 的字段中存储以下值类型:
Date:按提供的值进行存储。Time:将值的日期部分存储在值的时区域中。DateTime:将值的日期部分存储在值的时区域中。ActiveSupport::TimeWithZone:将值的日期部分存储在值的时区域中。String:存储字符串中指定的日期。Integer:将该值视为 UTC 时间戳,并将其转换为应用程序的已配置时区域。 然后,Mongoid 会存储从该时间戳中获取的日期。Float:将该值视为 UTC 时间戳,并将其转换为应用程序的已配置时区域。 然后,Mongoid 会存储从该时间戳中获取的日期。
由于转换 Time 或 DateTime 会丢弃时间部分,因此我们建议在将 String、Time 和 DateTime 对象赋值给字段之前将其显式转换为 Date。
注意
当数据库包含 Date字段的字符串值时,驱动程序会使用 Time.parse 方法解析该值,然后丢弃时间部分。Time.parse 将没有区域的值视为当地时间。要进一步学习;了解Mongoid 如何转换查询,请参阅 指定查询指南的 字段类型查询转换 部分。
日期时间
当您为定义为 DateTime 的字段赋值或对这些字段查询时,Mongoid 会在将其发送到MongoDB 服务器之前将该值转换为 UTC Time 值。 Mongoid 将该值与时区域一起保存在 DateTime对象中。 当您检索该值时,Mongoid 会将 UTC 时间转换为为应用程序配置的时区域。
以下示例创建一个 Ticket 类并将 purchased_at字段指定为 DateTime字段:
class Ticket include Mongoid::Document field :purchased_at, type: DateTime end
如果将整数或浮点值保存到 DateTime字段,则该值将被视为 UTC 格式的 Unix 时间戳。 以下示例将整数值保存到 purchased_at字段:
ticket.purchased_at = 1544803974 ticket.purchased_at # Outputs: Fri, 14 Dec 2018 16:12:54 +0000
如果将字符串值保存到 DateTime字段,Mongoid 会使用指定的时区域保存票证。 如果未指定时区域,Mongoid 会使用应用程序配置为默认时区的时区来保存值:
ticket.purchased_at = 'Mar 4, 2018 10:00:00 +01:00' ticket.purchased_at # Outputs: Sun, 04 Mar 2018 09:00:00 +0000
要学习;了解有关配置时区的更多信息,请参阅应用程序配置指南的时区配置部分。
注意
Mongoid 使用 Time.parse 方法将字符串值解析为 DateTime,该方法将没有时区的值视为采用当地时间。
时间戳
您可以通过在创建类时包含 Mongoid::Timestamps 模块,在类中包含时间戳字段。 当您包含 Mongoid::Timestamps 时,Mongoid 会在您的类中创建以下字段:
created_at:存储文档的创建时间。updated_at:存储文档上次更新时间。
以下示例创建了一个带有时间戳字段的 Post 类:
class Post include Mongoid::Document include Mongoid::Timestamps end
您也可以通过仅包含 Created 或 Updated 模块来选择仅包含 created_at 或 updated_at 字段。 以下示例创建了一个仅包含 created_at字段的Post 类以及一个仅包含 updated_at字段的Post 类:
class Post include Mongoid::Document include Mongoid::Timestamps::Created end class Post include Mongoid::Document include Mongoid::Timestamps::Updated end
您可以通过在包含该模块时设置 ::Short 选项,将时间戳字段名称缩短为 c_at 和 u_at:
class Post include Mongoid::Document include Mongoid::Timestamps::Short # For c_at and u_at. end class Post include Mongoid::Document include Mongoid::Timestamps::Created::Short # For c_at only. end class Post include Mongoid::Document include Mongoid::Timestamps::Updated::Short # For u_at only. end
您可以通过在方法调用上调用 timeless 方法来禁用为特定操作创建时间戳字段。 以下示例为 save 操作禁用时间戳:
post.timeless.save
regexp
您可以使用 Regexp 类型将正则表达式存储在字段中。
While MongoDB实施Perl兼容正则表达式 (PCRE),而 Mongoid 使用 Ruby 的Onigmo库。PCRE 和 Onigmo 提供的功能大体相似,但存在一些语法差异。示例,Onigmo 使用 \A 和 \z 来匹配字符串的开头和结尾,而 PCRE 使用 ^ 和 $。
当您将字段声明为 Regexp 时,Mongoid 在将结果存储到数据库时会将Ruby正则表达式转换为BSON正则表达式。 数据库会将该字段作为 Bson::Regexp::Raw实例返回。 您可以在 BSON::Regexp::Raw 实例上使用 compile 方法,将数据转换回Ruby正则表达式。
以下示例创建一个 Token 类并将 pattern字段指定为 Regexp:
class Token include Mongoid::Document field :pattern, type: Regexp end token = Token.create!(pattern: /hello.world/m) token.pattern # Outputs: /hello.world/m # Reload the token from the database token.reload token.pattern # Outputs: #<BSON::Regexp::Raw:0x0000555f505e4a20 @pattern="hello.world", @options="ms">
BigDecimal
您可以使用 BigDecimal 类型来存储更高精度的数字。 Mongoid 以两种不同的方式存储 BigDecimal 值,具体取决于您为 Mongoid.map_big_decimal_to_decimal128 配置属性设立的值:
如果设立为
true(默认),Mongoid 会将BigDecimal值存储为BSONDecimal128值。如果设立为
false,Mongoid 将BigDecimal值存储为字符串。
将 Mongoid.map_big_decimal_to_decimal128 选项设置为 true 时,请考虑以下限制:
Decimal128范围和精度有限。Decimal128的最大值约为10^6145,最小值约为-10^6145,最大精度为 34 位。 如果您要存储的值超出了这些限制,我们建议您将其存储为字符串。Decimal128接受带符号的NaN值,但BigDecimal不接受。 从数据库中检索有符号NaNDecimal128值作为BigDecimal时,会返回无符号值。Decimal128保留尾随零,但BigDecimal不保留。 因此,从数据库检索Decimal128值作为BigDecimal可能会导致精度损失。
注意
当您将 Mongoid.map_big_decimal_to_decimal128 选项设立为 false 并将 BigDecimal存储到非类型化字段中时,您无法将该字段作为 BigDecimal查询。 由于该值存储为字符串,因此在非类型化字段中查询 BigDecimal 值不会在数据库中找到该值。 要查找该值,必须首先将查询值转换为字符串。
您可以将该字段指定为 BigDecimal 类型(而不是非类型化)来避免此问题。
StringifiedSymbol
使用 StringifiedSymbol字段类型存储应作为符号向Ruby应用程序公开的值。 StringifiedSymbol 允许您使用符号,同时确保与其他驱动程序的互操作性。 此类型将数据库中的所有数据存储为字符串,并在应用程序读取时将字符串转换为符号。 无法直接转换为符号的值(例如整数和数组)将转换为字符串,然后转换为符号。
以下示例将 status字段定义为 StringifiedSymbol,并演示了如何存储和返回该字段:
class Post include Mongoid::Document field :status, type: StringifiedSymbol end # Save status as a symbol post = Post.new(status: :hello) # status is stored as "hello" on the database, but returned as a Symbol post.status # Outputs: :hello # Save status as a string post = Post.new(status: "hello") # status is stored as "hello" in the database, but returned as a Symbol post.status # Outputs: :hello
将字段类型指定为字符串或符号
在 Mongoid 中,您可以使用字符串或符号来指定某些字段类型,而不是使用它们的类名。 以下示例使用类名称、字符串和符号指定 order_num字段:
class Order include Mongoid::Document # Class Name field :order_num, type: Integer # Symbol field :order_num, type: :integer # String field :order_num, type: "integer" end
下表提供了可以指定为字符串或符号的字段类型:
类名 | 符号 | 字符串 |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
自定义字段类型
您可以创建自定义字段类型并定义 Mongoid 如何对其进行序列化和反序列化。 要创建自定义字段类型,请定义一个实现以下方法的类:
mongoize:获取自定义类型的实例并将其转换为MongoDB可以存储的对象。demongoize:从MongoDB获取一个对象并将其转换为自定义类型的实例。evolve:获取自定义类型的实例并将其转换为MongoDB可用于查询数据库的条件。
以下示例创建了一个名为 Point 的自定义字段类型并实现了上述方法:
class Point attr_reader :x, :y def initialize(x, y) @x, @y = x, y end # Converts an object of this instance into an array def mongoize [ x, y ] end class << self # Takes any possible object and converts it to how it is # 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 # Gets the object as it's stored in the database and instantiates # this custom class from it. def demongoize(object) if object.is_a?(Array) && object.length == 2 Point.new(object[0], object[1]) end end # Converts the object supplied to a criteria and converts it # into a queryable form. def evolve(object) case object when Point then object.mongoize else object end end end end
在前面的示例中,mongoize 实例方法接受自定义类型对象的实例,并将其转换为Array 以存储在数据库中。mongoize类方法接受所有类型的对象,并将它们转换为可以存储在数据库中的类似类型。Mongoid 在调用 getter 和 setter 方法时使用 mongoize 类方法。
demongoize 方法将存储的 Array 值转换为自定义 Point 类型。 Mongoid 在调用 getter 时使用此方法。
evolve 方法将自定义 Point 类型转换为可查询的 Array 类型,并将所有其他类型转换为 object。 Mongoid 在调用查询数据库的方法时使用此方法。
Phantom 自定义字段类型
您可以创建自定义字段类型,将与应用程序中分配的值不同的值保存到数据库中。 这对于在应用程序中拥有描述性值,同时在数据库中存储更紧凑的值非常有用。
以下示例创建了一个 ColorMapping 类型,该类型在应用程序中使用颜色的名称,但将颜色作为整数存储在数据库中:
class ColorMapping MAPPING = { 'black' => 0, 'white' => 1, }.freeze INVERSE_MAPPING = MAPPING.invert.freeze class << self def mongoize(object) MAPPING[object] end def demongoize(object) INVERSE_MAPPING[object] end 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 # Outputs: "white" # Sets "color" field to 0 in MongoDB profile.save!
动态字段
您可以通过在模型中包含 Mongoid::Attributes::Dynamic 模块来指示 Mongoid 动态创建字段。 这允许 Mongoid 基于任意哈希或基于已存储在数据库中的文档创建字段。
以下示例将创建一个带有动态字段的 Person 类:
class Person include Mongoid::Document include Mongoid::Attributes::Dynamic end
提示
您可以在同一个类中同时指定固定字段和动态字段。 在这种情况下,Mongoid 根据字段类型对待具有字段定义的属性的所有属性,并将所有其他属性视为动态属性。
在应用程序中使用动态字段时,首先必须通过以下方式之一设立值:
将属性哈希传递给构造函数。
使用
attributes=方法赋值。使用
[]=方法赋值。使用
write_attribute方法赋值。使用数据库中已存在的值。
如果最初未使用上述选项之一设立该值,则调用该属性将返回 NoMethodError。
保留字符
Mongoid 和MongoDB查询API都保留 . 字符来分隔嵌套文档中的字段名称,并保留字符串开头的 $ 字符来指示查询运算符。 因此,应避免在字段名称中使用这些字符。
如果您的应用程序需要使用这些字符,您可以通过调用 send 方法访问权限这些字段。 以下示例创建了一个 User 类,其中的字段包含保留字符。 然后,它使用 send 方法访问这些字段:
class User include Mongoid::Document field :"first.last", type: String field :"$_amount", type: Integer end user = User.first user.send(:"first.last") # Outputs: Mike.Trout user.send(:"$_amount") # Outputs: 42650000
您还可以通过调用 read_attribute 方法访问权限这些字段。
重要
由于更新和替换包含这些保留字符的字段需要特殊操作符,因此对这些字段调用 getter 和 setter 会引发 InvalidDotDollarAssignment 异常。