继承
Overview
在本指南中,您可以学习;了解如何在 Mongoid 模型中实现继承。继承允许您应用一个“父”类的特征应用于一个或多个“子”类。
Mongoid 支持顶层文档和嵌入式文档中的继承。 当子模型类从父类继承时,Mongoid 会将父类的字段、关联、验证和作用域复制到子类中。
指定继承
创建子模型类时,使用 <
字符实现从指定父类的继承。 以下模型类演示了如何在 Person
、Employee
和 Manager
模型之间创建父类和子类:
class Person include Mongoid::Document field :name, type: String end class Employee < Person field :company, type: String field :tenure, type: Integer scope :new_hire, ->{ where(:tenure.lt => 1) } end class Manager < Employee end
Mongoid 将 Person
、Employee
和 Manager
的实例保存在 people
集合中。 Mongoid 将 _type
鉴别器字段设置为文档中的模型类名称,以确保在执行读取操作时将文档作为预期类型返回。
嵌入式文档
您还可以在嵌入式关联中实现继承模式。 与顶级模型类的行为类似,Mongoid 会根据用于创建嵌入式文档的模型类,在嵌入式文档中设置 _type
鉴别器字段。
以下示例将嵌入式关联添加到 Person
模型,并为嵌入式 Info
类创建父模型和子模型:
class Person include Mongoid::Document field :name, type: String embeds_many :infos end ... class Info include Mongoid::Document field :active, type: Boolean embedded_in :person end class Phone < Info field :value, type: Float field :country, type: String end class Email < Info field :value, type: String field :category, type: String end
查询行为
当您查询子模型类时,查询仅返回其中 _type
字段的值与查询的类或其他子类匹配的文档。 示例,如果您对 Employee
类查询,则查询会返回 people
集合中 _type
值为 "Employee"
或 "Manager"
的文档。 所有其他鉴别器值都被视为 Person
父类的实例。
在查询父类(例如 Person
)时,Mongoid 返回满足以下任一条件的文档:
鉴别器值是父类或任何子类的名称。 示例,
"Person"
、"Employee"
或"Manager"
。缺少鉴别器值。
鉴别器值不会映射到父类或其任何子类。 示例,
"Director"
或"Specialist"
。
更改鉴别器键
由于以下任一原因,您可能会将鉴别器键从默认字段名称 _type
更改为其他值:
优化:您可以选择更短的键,例如
_t
。与现有系统的一致性:您可能使用具有预定义键的现有系统或数据集。
您可以在类级别或全局级别更改鉴别器键。 要在类级别更改鉴别器键,您可以使用 discriminator_key
方法在父类上设立自定义键名称。
以下示例演示了如何在定义模型类时设立自定义鉴别器键:
class Person include Mongoid::Document field :name, type: String self.discriminator_key = "sub_type" end
当您创建 Person
或其任何子类的实例时,Mongoid 会将 sub_type
字段添加到MongoDB中的文档中。
注意
您只能更改父类的鉴别器键。 如果您在任何子类上设立自定义键,Mongoid 会引发错误。
如果在定义子类后更改鉴别器键,Mongoid 会添加新键字段,但旧字段保持不变。 示例,假设您在定义模型类后将以下代码添加到应用程序中:
Person.discriminator_key = "sub_type"
在本例中,当您创建 Employee
等子类的实例时,Mongoid 会将 sub_type
和 _type
字段添加到文档中。
您还可以在全局级别更改鉴别器键,以便所有类都使用指定的键而不是 _type
字段。
在定义任何模型类之前,您可以通过将以下代码添加到应用程序中设立全局密钥:
Mongoid.discriminator_key = "sub_type"
所有类都使用 sub_type
作为鉴别器键,并且不包含 _type
字段。
注意
在定义任何子类之前,您必须在全局级别上设立鉴别器键,以便这些类使用该全局值。 如果在定义子类后设立全局键,则保存的文档将包含默认的_type
字段。
更改鉴别器值
您可以自定义 Mongoid 在MongoDB中设置为鉴别器值的值。 在定义类时使用 discriminator_value
方法来自定义鉴别器值,如以下示例所示:
class Employee include Mongoid::Document field :company, type: String self.discriminator_value = "Worker" end
当您创建 Employee
的实例时,文档的 _type
鉴别器字段的值为 "Worker"
,而不是类名称。
注意
由于鉴别器值自定义是在子类中声明的,因此您必须在发送查询之前加载该查询的子类。
在前面的示例中,如果返回的文档包含 Employee
的实例,则必须先加载 Employee
类定义,然后再对 Person
查询。 自动加载无法解析鉴别器值 "Worker"
以将文档作为 Employee
的实例返回。
嵌入式关联
您可以通过赋值或使用 build
和 create
方法,在嵌入式关联中创建任何类型的父类或子类。 您可以将所需的模型类作为第二个参数传递给 build
和 create
方法,以指示 Mongoid 将该特定实例创建为嵌入式文档。
以下代码创建一个 Employee
实例,然后演示如何使用不同的创建方法添加嵌入式文档:
# Creates a new Employee instance e = Employee.create( name: "Lance Huang", company: "XYZ Communications", tenure: 2 ) # Builds an Info object e.infos.build({ active: true }) # Builds a Phone object e.infos.build( { active: true, value: 1239007777, country: "USA" }, Phone ) # Creates an Email object e.infos.create( { active: true, value: "l.huang@company.com", category: "work" }, Email ) # Creates and assigns an Email object p = Email.new(active: false, value: "lanceh11@mymail.com", category: "personal" ) e.infos << p # Saves the Employee instance to database e.save
以下文档存储在 people
数据库中:
{ "_id": {...}, "name": "Lance Huang", "company": "XYZ Communications", "tenure": 2, "_type": "Employee", "infos": [ { "_id": {...}, "active": true, "value": "l.huang@company.com", "category": "work", "_type": "Email" }, { "_id": {...}, "active": false, "value": "lanceh11@mymail.com", "category": "personal", "_type": "Email" }, { "_id": {...}, "active": true, "_type": "Info" }, { "_id": {...}, "active": true, "value": 1239007777, "country": "USA", "_type": "Phone" } ] }
持久性上下文
您可以将子类的持久性上下文从其父类的持久性上下文更改为其他位置,以将文档存储在默认之外的位置。 通过使用 store_in
方法,您可以将子类实例存储在与父模型实例不同的集合、数据库或集群中。
以下模型定义演示了如何使用 store_in
方法将 Employee
和 Manager
的实例存储在 people
集合以外的其他集合中:
class Person include Mongoid::Document end class Employee < Person # Specifies "employees" as target collection store_in collection: :employees end class Manager < Employee # Specifies "managers" as target collection store_in collection: :managers end
注意
Mongoid 仍将鉴别器字段添加到存储的文档中。
如果您对某些子类设立备用目标集合,则没有指定集合的类的实例将存储在与父类关联的集合中。
注意
当您更改子类的目标集合时,该类的实例不会出现在父类的查询结果中。