|
| 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 | + |
| 235 | + "category": "work", |
| 236 | + "_type": "Email" |
| 237 | + }, |
| 238 | + { |
| 239 | + "_id": {...}, |
| 240 | + "active": false, |
| 241 | + |
| 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 |
0 commit comments