Skip to content

Commit 9192fed

Browse files
authored
Merge branch 'standardized' into DOCSP-45358-docuents
2 parents b217789 + 893e73e commit 9192fed

File tree

3 files changed

+387
-0
lines changed

3 files changed

+387
-0
lines changed

source/data-modeling.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,12 @@ Model Your Data
1515
:caption: Data Modeling
1616

1717
Documents </data-modeling/documents>
18+
Inheritance </data-modeling/inheritance>
1819

1920
In this section, you can learn how to model data in {+odm+}.
2021

2122
- :ref:`mongoid-modeling-documents`: Learn about the ``Document``
2223
module.
24+
25+
- :ref:`mongoid-modeling-inheritance`: Learn how to implement
26+
inheritance in your model classes.
Lines changed: 307 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,307 @@
1+
.. _mongoid-modeling-inheritance:
2+
3+
===========
4+
Inheritance
5+
===========
6+
7+
.. facet::
8+
:name: genre
9+
:values: reference
10+
11+
.. meta::
12+
:keywords: ruby framework, odm, relationship, code example, polymorphic
13+
14+
.. contents:: On this page
15+
:local:
16+
:backlinks: none
17+
:depth: 2
18+
:class: singlecol
19+
20+
Overview
21+
--------
22+
23+
In this guide, you can learn how to implement **inheritance** into your
24+
{+odm+} models. Inheritance allows you to apply the characteristics of
25+
one "parent" class to one or more "child" classes.
26+
27+
{+odm+} supports inheritance in top-level and embedded documents.
28+
When a child model class inherits from a parent class, {+odm+} copies
29+
the parent class's fields, associations, validations, and scopes to
30+
the child class.
31+
32+
Assign Inheritance
33+
------------------
34+
35+
When creating a child model class, use the ``<`` character to implement
36+
inheritance from a specified parent class. The following model classes
37+
demonstrate how to create parent and child classes between the
38+
``Person``, ``Employee``, and ``Manager`` models:
39+
40+
.. literalinclude:: /includes/data-modeling/inheritance.rb
41+
:start-after: start-simple-inheritance
42+
:end-before: end-simple-inheritance
43+
:language: ruby
44+
:emphasize-lines: 7, 14
45+
:dedent:
46+
47+
{+odm+} saves instances of ``Person``, ``Employee``, and ``Manager`` in
48+
the ``people`` collection. {+odm+} sets the ``_type`` discriminator
49+
field to the model class name in documents to ensure that documents are
50+
returned as the expected types when you perform read operations.
51+
52+
Embedded Documents
53+
~~~~~~~~~~~~~~~~~~
54+
55+
You can also implement an inheritance pattern in embedded associations.
56+
Similar to the behavior of top-level model classes, {+odm+} sets the
57+
``_type`` discriminator field in embedded documents depending on the
58+
model class used to create them.
59+
60+
The following example adds an embedded association to the ``Person``
61+
model and creates parent and child models for the embedded ``Info``
62+
class:
63+
64+
.. literalinclude:: /includes/data-modeling/inheritance.rb
65+
:start-after: start-embedded-inheritance
66+
:end-before: end-embedded-inheritance
67+
:language: ruby
68+
:emphasize-lines: 5, 14, 17, 22
69+
:dedent:
70+
71+
Query Behavior
72+
~~~~~~~~~~~~~~
73+
74+
When you query on a child model class, the query returns only documents
75+
in which the value of the ``_type`` field matches the queried class or
76+
further child classes. For example, if you query on the ``Employee``
77+
class, the query returns documents from the ``people`` collection in
78+
which the ``_type`` value is either ``"Employee"`` or ``"Manager"``. All
79+
other discriminator values are considered as instances of the parent
80+
``Person`` class.
81+
82+
When querying on a parent class such as ``Person``, {+odm+} returns
83+
documents that meet any of the following criteria:
84+
85+
- Discriminator value is the name of the parent class or any of the
86+
child classes. For example, ``"Person"``, ``"Employee"``, or ``"Manager"``.
87+
88+
- Lacks a discriminator value.
89+
90+
- Discriminator value does not map to either the parent or any of its
91+
child classes. For example, ``"Director"`` or ``"Specialist"``.
92+
93+
Change the Discriminator Key
94+
----------------------------
95+
96+
You might change the discriminator key from the default field name
97+
``_type`` for any of the following reasons:
98+
99+
- Optimization: You can select a shorter key such as ``_t``.
100+
101+
- Consistency with an existing system: You might be using an existing
102+
system or dataset that has predefined keys.
103+
104+
You can change the discriminator key on the class level
105+
or on the global level. To change the discriminator key on the class
106+
level, you can set the custom key name on the parent class by using the
107+
``discriminator_key()`` method.
108+
109+
The following example demonstrates how to set a custom discriminator key
110+
when defining a model class:
111+
112+
.. code-block:: ruby
113+
:emphasize-lines: 6
114+
115+
class Person
116+
include Mongoid::Document
117+
118+
field :name, type: String
119+
120+
self.discriminator_key = "sub_type"
121+
end
122+
123+
When you create an instance of ``Person`` or any of its child classes,
124+
{+odm+} adds the ``sub_type`` field to documents in MongoDB.
125+
126+
.. note::
127+
128+
You can change the discriminator key only on the parent class.
129+
{+odm+} raises an error if you set a custom key on any child
130+
class.
131+
132+
If you change the discriminator key after defining a child class, {+odm+}
133+
adds the new key field, but the old field is unchanged. For example, suppose
134+
you add the following code to your application *after* defining your
135+
model classes:
136+
137+
.. code-block:: ruby
138+
139+
Person.discriminator_key = "sub_type"
140+
141+
In this case, when you create an instance of a child class such as
142+
``Employee``, {+odm+} adds both the ``sub_type`` and ``_type`` fields to
143+
the document.
144+
145+
You can also change the discriminator key at the global level, so that
146+
all classes use the specified key instead of the ``_type`` field.
147+
148+
You can set a global key by adding the following code to your
149+
application *before* defining any model classes:
150+
151+
.. code-block:: ruby
152+
153+
Mongoid.discriminator_key = "sub_type"
154+
155+
All classes use ``sub_type`` as the discriminator key and do not include
156+
the ``_type`` field.
157+
158+
.. note::
159+
160+
You must set the discriminator key on the global level before defining
161+
any child classes for the classes to use that global value. If you set
162+
the global key after defining child classes, your saved documents
163+
contain the default ``_type`` field.
164+
165+
Change the Discriminator Value
166+
------------------------------
167+
168+
You can customize the value that {+odm+} sets as the discriminator value
169+
in MongoDB. Use the ``discriminator_value()`` method when defining a
170+
class to customize the discriminator value, as shown in the following
171+
example:
172+
173+
.. code-block:: ruby
174+
:emphasize-lines: 6
175+
176+
class Employee
177+
include Mongoid::Document
178+
179+
field :company, type: String
180+
181+
self.discriminator_value = "Worker"
182+
end
183+
184+
When you create an instance of ``Employee``, the document's ``_type``
185+
discriminator field has a value of ``"Worker"`` instead of the class
186+
name.
187+
188+
.. note::
189+
190+
Because the discriminator value customization is declared in child classes,
191+
you must load the child classes retrieved by a query *before* sending
192+
that query.
193+
194+
In the preceding example, the ``Employee`` class definition must be
195+
loaded before you query on ``Person`` if the returned documents include
196+
instances of ``Employee``. Autoloading cannot resolve the
197+
discriminator value ``"Worker"`` to return documents as instances of
198+
``Employee``.
199+
200+
Embedded Associations
201+
---------------------
202+
203+
You can create any type of parent class or child class in an embedded
204+
association by assignment or by using the ``build()`` and ``create()``
205+
methods. You can pass desired model class as the second parameter to the
206+
``build()`` and ``create()`` methods to instruct {+odm+} to create that
207+
specific instance as an emdedded document.
208+
209+
The following code creates an instance of ``Employee``, then
210+
demonstrates how to add embedded documents by using the different
211+
creation methods:
212+
213+
.. literalinclude:: /includes/data-modeling/inheritance.rb
214+
:start-after: start-association-operations
215+
:end-before: end-association-operations
216+
:language: ruby
217+
:dedent:
218+
219+
The following document is stored in the ``people`` database:
220+
221+
.. code-block:: json
222+
:emphasize-lines: 13, 20, 25, 32
223+
224+
{
225+
"_id": {...},
226+
"name": "Lance Huang",
227+
"company": "XYZ Communications",
228+
"tenure": 2,
229+
"_type": "Employee",
230+
"infos": [
231+
{
232+
"_id": {...},
233+
"active": true,
234+
"value": "[email protected]",
235+
"category": "work",
236+
"_type": "Email"
237+
},
238+
{
239+
"_id": {...},
240+
"active": false,
241+
"value": "[email protected]",
242+
"category": "personal",
243+
"_type": "Email"
244+
},
245+
{
246+
"_id": {...},
247+
"active": true,
248+
"_type": "Info"
249+
},
250+
{
251+
"_id": {...},
252+
"active": true,
253+
"value": 1239007777,
254+
"country": "USA",
255+
"_type": "Phone"
256+
}
257+
]
258+
}
259+
260+
Persistence Contexts
261+
--------------------
262+
263+
You can change the persistence context of a child class from
264+
the persistence context of its parent to store the document in a
265+
different location than the default. By using the ``store_in()`` method,
266+
you can store an instance of a child class in a different collection,
267+
database, or cluster than an instance of the parent model.
268+
269+
The following model definitions demonstrate how to use the
270+
``store_in()`` method to store instances of ``Employee`` and ``Manager``
271+
in a different collection than the ``people`` collection:
272+
273+
.. code-block:: ruby
274+
:emphasize-lines: 7, 12
275+
276+
class Person
277+
include Mongoid::Document
278+
end
279+
280+
class Employee < Person
281+
# Specifies "employees" as target collection
282+
store_in collection: :employees
283+
end
284+
285+
class Manager < Employee
286+
# Specifies "managers" as target collection
287+
store_in collection: :managers
288+
end
289+
290+
.. note::
291+
292+
{+odm+} still adds the discriminator field to stored documents.
293+
294+
If you set an alternate target collection on some child classes
295+
and not others, instances of the classes without specified collections
296+
are stored in the collection associated with the parent class.
297+
298+
.. note::
299+
300+
When you change the target collection for a child class, instances of
301+
that class do not appear in the results from queries on the parent
302+
class.
303+
304+
Additional Information
305+
----------------------
306+
307+
.. TODO add links to persistence docs
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# start-simple-inheritance
2+
class Person
3+
include Mongoid::Document
4+
5+
field :name, type: String
6+
end
7+
8+
class Employee < Person
9+
field :company, type: String
10+
field :tenure, type: Integer
11+
12+
scope :new_hire, ->{ where(:tenure.lt => 1) }
13+
end
14+
15+
class Manager < Employee
16+
end
17+
# end-simple-inheritance
18+
19+
# start-embedded-inheritance
20+
class Person
21+
include Mongoid::Document
22+
23+
field :name, type: String
24+
embeds_many :infos
25+
end
26+
27+
...
28+
29+
class Info
30+
include Mongoid::Document
31+
32+
field :active, type: Boolean
33+
embedded_in :person
34+
end
35+
36+
class Phone < Info
37+
field :value, type: Float
38+
field :country, type: String
39+
end
40+
41+
class Email < Info
42+
field :value, type: String
43+
field :category, type: String
44+
end
45+
# end-embedded-inheritance
46+
47+
# start-association-operations
48+
# Creates a new Employee instance
49+
e = Employee.create(
50+
name: "Lance Huang",
51+
company: "XYZ Communications",
52+
tenure: 2
53+
)
54+
55+
# Builds an Info object
56+
e.infos.build({ active: true })
57+
58+
# Builds a Phone object
59+
e.infos.build(
60+
{ active: true, value: 1239007777, country: "USA" },
61+
Phone
62+
)
63+
64+
# Creates an Email object
65+
e.infos.create(
66+
{ active: true, value: "[email protected]", category: "work" },
67+
Email
68+
)
69+
70+
# Creates and assigns an Email object
71+
p = Email.new(active: false, value: "[email protected]", category: "personal" )
72+
e.infos << p
73+
74+
# Saves the Employee instance to database
75+
e.save
76+
# end-association-operations

0 commit comments

Comments
 (0)