Overview
在本指南中,您可以学习;了解Mongoid 支持的字段类型,您可以使用这些字段类型来定义MongoDB文档的模式。
MongoDB使用 BSON 类型来表示存储在文档字段中的数据类型。要在 Mongoid应用程序中使用BSON数据,Mongoid 必须在运行时将BSON 类型转换为Ruby类型。 示例,从数据库检索文档时,Mongoid 会将BSON double 类型转换为使用Ruby Float 类型。 当您再次保存文档时,Mongoid 会将字段转换回BSON double。
要学习;了解有关在 Mongoid 中对文档进行建模的更多信息,请参阅《在模型中包含文档模块》指南。
注意
修改模型类中的字段定义不会更改数据库中存储的任何数据。 要更改数据库中字段的数据类型,必须再次重新保存数据。
字段类型
您可以使用 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 异常。