Docs 菜单

继承

在本指南中,您可以学习;了解如何在 Mongoid 模型中实现继承。继承允许您应用一个“父”类的特征应用于一个或多个“子”类。

Mongoid 支持顶层文档和嵌入式文档中的继承。 当子模型类从父类继承时,Mongoid 会将父类的字段、关联、验证和作用域复制到子类中。

创建子模型类时,使用 < 字符实现从指定父类的继承。 以下模型类演示了如何在 PersonEmployeeManager 模型之间创建父类和子类:

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 将 PersonEmployeeManager 的实例保存在 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 的实例返回。

您可以通过赋值或使用 buildcreate 方法,在嵌入式关联中创建任何类型的父类或子类。 您可以将所需的模型类作为第二个参数传递给 buildcreate 方法,以指示 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 方法将 EmployeeManager 的实例存储在 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 仍将鉴别器字段添加到存储的文档中。

如果您对某些子类设立备用目标集合,则没有指定集合的类的实例将存储在与父类关联的集合中。

注意

当您更改子类的目标集合时,该类的实例不会出现在父类的查询结果中。

要学习;了解有关为操作配置目标集合的更多信息,请参阅持久性配置指南。