继承
概述
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
都会保存在 canvases 集合中。为了确保从数据库加载时返回正确的文档,会存储一个额外的属性 _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=
设置器的调用。现在,在创建 Rectangle 或 Circle 时,将添加一个 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"
在这种情况下,在创建 Rectangle 或 Circle 时,将同时存在一个 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=
设置器的调用。现在,在创建 Circle
时,文档将包含一个具有 _type
键(或更改后的 discriminator_key
)的值 "round thing" 的字段。
注意
由于判别器值覆盖在子类中声明,因此查询可能找到的子类必须在发送查询之前加载。在上面的示例中,当在Shape
上查询时,必须加载Circle
类的定义,如果返回的文档可能是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" }
如果只有部分子类设置了集合,而其他子类没有设置,则设置了集合的子类将文档存储在这些集合中,而没有设置集合的子类将文档存储在父类的集合中。
注意
请注意,改变子类的存储集合会导致该子类的文档在查询其父类时不再出现在结果中。