继承
Overview
Mongoid 支持顶层文档和嵌入式文档中的继承。当子文档从父文档继承时,父文档的字段、关联、验证和范围会复制到子文档中。
class Canvas include Mongoid::Document field :name, type: String embeds_many :shapes end class Browser < Canvas field :version, type: Integer scope :recent, ->{ where(:version.gt => 3) } end class Firefox < Browser end class Shape include Mongoid::Document field :x, type: Integer field :y, type: Integer embedded_in :canvas end class Circle < Shape field :radius, type: Float end class Rectangle < Shape field :width, type: Float field :height, type: Float end
在上面的示例中,Canvas
、Browser
和 Firefox
将全部保存在画布集合中。为了确保从数据库加载时返回正确的文档,存储附加属性 _type
。嵌入式文档 Circle
(圆形)、Rectangle
(矩形) 和 Shape
(形状) 也是如此。
注意
搜索 Circle
时,查询将仅返回形状集合中的文档,其中 _type
(或是设置的鉴别器键)字段的值为 Circle
(或是设置的鉴别器值),所有其他鉴别器值都将被视为 Shape 类的对象。
同样,当按父类(本例中为 Canvas
)查询时,凡是集合中没有鉴别器值,或其鉴别器值未映射到父类或其任何后代的文档,都将作为父类的实例返回。
更改鉴别器键
Mongoid 支持从默认的 _type
更改鉴别键。在某些情况下,可能需要执行此操作:
为了进行优化:用户可能想要使用更短的键,比如
_t
。尝试使用现有系统:用户可能正在使用具有预定义键的现有系统或数据集。
有两种方法可以更改鉴别器键:一种是在类级别,另一种是在全局级别。要在类级别上更改鉴别器键,用户可以使用 discriminator_key=
方法直接在父类上进行设置。以上面的例子为例:
class Shape include Mongoid::Document field :x, type: Integer field :y, type: Integer embedded_in :canvas self.discriminator_key = "shape_type" end class Circle < Shape field :radius, type: Float end class Rectangle < Shape field :width, type: Float field :height, type: Float end
此处向父类添加了对 discriminator_key=
setter 的调用。现在,在创建矩形或圆形时,将添加 shape_type
字段。
请注意,鉴别器键只能在父类中修改,如果尝试在子类中设置它,将会引发错误。
如果在创建子类后更改了鉴别器键,则会添加一个带有新鉴别器键值的新字段,而旧字段将保持不变。例如:
class Shape include Mongoid::Document field :x, type: Integer field :y, type: Integer embedded_in :canvas end class Circle < Shape field :radius, type: Float end class Rectangle < Shape field :width, type: Float field :height, type: Float end Shape.discriminator_key = "shape_type"
在本例中,创建矩形或圆形时,将同时存在 shape_type
和 _type
字段,它们对应于所创建形状的默认值都是 Rectangle
(矩形)或 Circle
(圆形)。
也可以在全局级别上设置鉴别器键。这意味着,所有类都将使用全局设置的鉴别器键而不是 _type
。以上面的例子为例:
Mongoid.discriminator_key = "_the_type" class Shape include Mongoid::Document field :x, type: Integer field :y, type: Integer embedded_in :canvas end class Circle < Shape field :radius, type: Float end class Rectangle < Shape field :width, type: Float field :height, type: Float end
设置全局鉴别键后,所有类都将使用 _the_type
作为鉴别键,不包含 _type
字段。
请注意,在全局级别定义鉴别器键时,必须在定义子类之前设置它,才能让子类使用该全局值。但是,在全局级别上,如果用户未在定义子类之前设置鉴别器键,则鉴别器字段将使用默认的 _type
,而不是该子类中的新全局设置。
更改鉴别器值
Mongoid 还支持将鉴别器值从默认值(即类名)更改为其他值。可以对该特定类使用 discriminator_value=
方法,以更改鉴别器值。
以上面的例子为例:
class Shape include Mongoid::Document field :x, type: Integer field :y, type: Integer embedded_in :canvas end class Circle < Shape field :radius, type: Float self.discriminator_value = "round thing" end class Rectangle < Shape field :width, type: Float field :height, type: Float end
此处向 Circle
添加了对 discriminator_value=
setter 的调用。现在,创建 Circle
(圆形)时,文档将包含一个字段,其键为 _type
(或是更改后的 discriminator_key
),值为“round thing”(圆形的东西)。
注意
由于鉴别器值覆盖是在子类中声明的,因此在发送该查询之前,必须先加载查询可能找到的子类。在上面的示例中,如果返回的文档可能是 Circle
的实例,则在查询 Shape
时必须加载 Circle
类定义(因为自动加载不会将 "round thing"
解析为 Circle
)。
查询子类
子类查询以正常方式处理,虽然文档都在同一个集合中,但查询只会返回正确类型的文档,类似于 ActiveRecord 中的单表继承。
# Returns Canvas documents and subclasses Canvas.where(name: "Paper") # Returns only Firefox documents Firefox.where(name: "Window 1")
关联
您可以通过常规设置或通过关联上的构建和创建方法,将任意类型的子类添加到具有一个或多个关联的:
firefox = Firefox.new # Builds a Shape object firefox.shapes.build({ x: 0, y: 0 }) # Builds a Circle object firefox.shapes.build({ x: 0, y: 0 }, Circle) # Creates a Rectangle object firefox.shapes.create({ x: 0, y: 0 }, Rectangle) rect = Rectangle.new(width: 100, height: 200) firefox.shapes
持久性上下文
Mongoid 允许子类的持久化上下文与其父类的持久化上下文不同。这意味着,使用 store_in
方法,我们可以将子类的文档存储在与其父类不同的集合(以及不同的数据库、客户端)中:
class Shape include Mongoid::Document store_in collection: :shapes end class Circle < Shape store_in collection: :circles end class Square < Shape store_in collection: :squares end Shape.create! Circle.create! Square.create!
在子级上设置集合会导致这些子级的文档存储在设置集合中,而不是存储在父级集合中:
> db.shapes.find() { "_id" : ObjectId("62fe9a493282a43d6b725e10"), "_type" : "Shape" } > db.circles.find() { "_id" : ObjectId("62fe9a493282a43d6b725e11"), "_type" : "Circle" } > db.squares.find() { "_id" : ObjectId("62fe9a493282a43d6b725e12"), "_type" : "Square" }
如果在一些子类上设置了集合,而在其他子类上没有设置集合,则具有所设置集合的子类将在这些集合中存储文档,而没有所设置集合的子类将在父类的集合中存储文档。
注意
请注意,更改存储子类的集合将导致在查询其父类的结果中找不到该子类的文档。