1 | // Copyright IBM Corp. 2013,2019. All Rights Reserved.
|
2 | // Node module: loopback-datasource-juggler
|
3 | // This file is licensed under the MIT License.
|
4 | // License text available at https://opensource.org/licenses/MIT
|
5 |
|
6 | ;
|
7 |
|
8 | /*!
|
9 | * Dependencies
|
10 | */
|
11 | const relation = require('./relation-definition');
|
12 | const RelationDefinition = relation.RelationDefinition;
|
13 |
|
14 | module.exports = RelationMixin;
|
15 |
|
16 | /**
|
17 | * RelationMixin class. Use to define relationships between models.
|
18 | *
|
19 | * @class RelationMixin
|
20 | */
|
21 | function RelationMixin() {
|
22 | }
|
23 |
|
24 | /**
|
25 | * Define a "one to many" relationship by specifying the model name.
|
26 | *
|
27 | * Examples:
|
28 | * ```
|
29 | * User.hasMany(Post, {as: 'posts', foreignKey: 'authorId'});
|
30 | * ```
|
31 | *
|
32 | * ```
|
33 | * Book.hasMany(Chapter);
|
34 | * ```
|
35 | * Or, equivalently:
|
36 | * ```
|
37 | * Book.hasMany('chapters', {model: Chapter});
|
38 | * ```
|
39 | *
|
40 | * Query and create related models:
|
41 | *
|
42 | * ```js
|
43 | * Book.create(function(err, book) {
|
44 | *
|
45 | * // Create a chapter instance ready to be saved in the data source.
|
46 | * var chapter = book.chapters.build({name: 'Chapter 1'});
|
47 | *
|
48 | * // Save the new chapter
|
49 | * chapter.save();
|
50 | *
|
51 | * // you can also call the Chapter.create method with the `chapters` property which will build a chapter
|
52 | * // instance and save the it in the data source.
|
53 | * book.chapters.create({name: 'Chapter 2'}, function(err, savedChapter) {
|
54 | * // this callback is optional
|
55 | * });
|
56 | *
|
57 | * // Query chapters for the book
|
58 | * book.chapters(function(err, chapters) {
|
59 | * // all chapters with bookId = book.id
|
60 | * console.log(chapters);
|
61 | * });
|
62 | *
|
63 | * // Query chapters for the book with a filter
|
64 | * book.chapters({where: {name: 'test'}, function(err, chapters) {
|
65 | * // All chapters with bookId = book.id and name = 'test'
|
66 | * console.log(chapters);
|
67 | * });
|
68 | * });
|
69 | * ```
|
70 | *
|
71 | * @param {Object|String} modelTo Model object (or String name of model) to which you are creating the relationship.
|
72 | * @options {Object} params Configuration parameters; see below.
|
73 | * @property {String} as Name of the property in the referring model that corresponds to the foreign key field in the related model.
|
74 | * @property {String} foreignKey Property name of foreign key field.
|
75 | * @property {String} polymorphic Define a polymorphic relation name.
|
76 | * @property {String} through Name of the through model.
|
77 | * @property {String} keyThrough Property name of the foreign key in the through model.
|
78 | * @property {Object|Function} scope Explicitly define additional scopes.
|
79 | * @property {Boolean} invert Specify if the relation is inverted.
|
80 | * @property {Object} model The model object.
|
81 | */
|
82 | RelationMixin.hasMany = function hasMany(modelTo, params) {
|
83 | return RelationDefinition.hasMany(this, modelTo, params);
|
84 | };
|
85 |
|
86 | /**
|
87 | * Declare "belongsTo" relation that sets up a one-to-one connection with another model, such that each
|
88 | * instance of the declaring model "belongs to" one instance of the other model.
|
89 | *
|
90 | * For example, if an application includes users and posts, and each post can be written by exactly one user.
|
91 | * The following code specifies that `Post` has a reference called `author` to the `User` model via the `userId` property of `Post`
|
92 | * as the foreign key.
|
93 | * ```
|
94 | * Post.belongsTo(User, {as: 'author', foreignKey: 'userId'});
|
95 | * ```
|
96 | * You can then access the author in one of the following styles.
|
97 | * Get the User object for the post author asynchronously:
|
98 | * ```
|
99 | * post.author(callback);
|
100 | * ```
|
101 | * Get the User object for the post author synchronously:
|
102 | * ```
|
103 | * post.author();
|
104 | * ```
|
105 | * Set the author to be the given user:
|
106 | * ```
|
107 | * post.author(user)
|
108 | * ```
|
109 | * Examples:
|
110 | *
|
111 | * Suppose the model Post has a *belongsTo* relationship with User (the author of the post). You could declare it this way:
|
112 | * ```js
|
113 | * Post.belongsTo(User, {as: 'author', foreignKey: 'userId'});
|
114 | * ```
|
115 | *
|
116 | * When a post is loaded, you can load the related author with:
|
117 | * ```js
|
118 | * post.author(function(err, user) {
|
119 | * // the user variable is your user object
|
120 | * });
|
121 | * ```
|
122 | *
|
123 | * The related object is cached, so if later you try to get again the author, no additional request will be made.
|
124 | * But there is an optional boolean parameter in first position that set whether or not you want to reload the cache:
|
125 | * ```js
|
126 | * post.author(true, function(err, user) {
|
127 | * // The user is reloaded, even if it was already cached.
|
128 | * });
|
129 | * ```
|
130 | * This optional parameter default value is false, so the related object will be loaded from cache if available.
|
131 | *
|
132 | * @param {Class|String} modelTo Model object (or String name of model) to which you are creating the relationship.
|
133 | * @options {Object} params Configuration parameters; see below.
|
134 | * @property {String} as Name of the property in the referring model that corresponds to the foreign key field in the related model.
|
135 | * @property {String} primaryKey Property name of primary key field.
|
136 | * @property {String} foreignKey Name of foreign key property.
|
137 | * @property {Object|Function} scope Explicitly define additional scopes.
|
138 | * @property {Object} properties Properties inherited from the parent object.
|
139 | * @property {Object} options Property level options.
|
140 | * @property {Boolean} options.invertProperties Specify if the properties should be inverted.
|
141 | */
|
142 | RelationMixin.belongsTo = function(modelTo, params) {
|
143 | return RelationDefinition.belongsTo(this, modelTo, params);
|
144 | };
|
145 |
|
146 | /**
|
147 | * A hasAndBelongsToMany relation creates a direct many-to-many connection with another model, with no intervening model.
|
148 | *
|
149 | * For example, if your application includes users and groups, with each group having many users and each user appearing
|
150 | * in many groups, you could declare the models this way:
|
151 | * ```
|
152 | * User.hasAndBelongsToMany('groups', {model: Group, foreignKey: 'groupId'});
|
153 | * ```
|
154 | * Then, to get the groups to which the user belongs:
|
155 | * ```
|
156 | * user.groups(callback);
|
157 | * ```
|
158 | * Create a new group and connect it with the user:
|
159 | * ```
|
160 | * user.groups.create(data, callback);
|
161 | * ```
|
162 | * Connect an existing group with the user:
|
163 | * ```
|
164 | * user.groups.add(group, callback);
|
165 | * ```
|
166 | * Remove the user from the group:
|
167 | * ```
|
168 | * user.groups.remove(group, callback);
|
169 | * ```
|
170 | *
|
171 | * @param {String|Object} modelTo Model object (or String name of model) to which you are creating the relationship.
|
172 | * @options {Object} params Configuration parameters; see below.
|
173 | * @property {String} as Name of the property in the referring model that corresponds to the foreign key field in the related model.
|
174 | * @property {String} foreignKey Property name of foreign key field.
|
175 | * @property {String} throughTable The table name of the through model.
|
176 | * @property {String} through Name of the through model.
|
177 | * @property {String} polymorphic Define a polymorphic relation name.
|
178 | * @property {Object|Function} scope Explicitly define additional scopes.
|
179 | * @property {Object} model The model object.
|
180 | */
|
181 | RelationMixin.hasAndBelongsToMany = function hasAndBelongsToMany(modelTo, params) {
|
182 | return RelationDefinition.hasAndBelongsToMany(this, modelTo, params);
|
183 | };
|
184 |
|
185 | /**
|
186 | * Define a "one to one" relationship by specifying the model name.
|
187 | *
|
188 | * Examples:
|
189 | * ```
|
190 | * Supplier.hasOne(Account, {as: 'account', foreignKey: 'supplierId'});
|
191 | * ```
|
192 | *
|
193 | * If the target model doesn’t have a foreign key property, LoopBack will add a property with the same name.
|
194 | *
|
195 | * The type of the property will be the same as the type of the target model’s id property.
|
196 | *
|
197 | * Please note the foreign key property is defined on the target model (in this example, Account).
|
198 | *
|
199 | * If you don’t specify them, then LoopBack derives the relation name and foreign key as follows:
|
200 | * - Relation name: Camel case of the model name, for example, for the “supplier” model the relation is “supplier”.
|
201 | * - Foreign key: The relation name appended with Id, for example, for relation name “supplier” the default foreign key is “supplierId”.
|
202 | *
|
203 | * Build a new account for the supplier with the supplierId to be set to the id of the supplier.
|
204 | * ```js
|
205 | * var supplier = supplier.account.build(data);
|
206 | * ```
|
207 | *
|
208 | * Create a new account for the supplier. If there is already an account, an error will be reported.
|
209 | * ```js
|
210 | * supplier.account.create(data, function(err, account) {
|
211 | * ...
|
212 | * });
|
213 | * ```
|
214 | *
|
215 | * Find the supplier's account model.
|
216 | * ```js
|
217 | * supplier.account(function(err, account) {
|
218 | * ...
|
219 | * });
|
220 | * ```
|
221 | *
|
222 | * Update the associated account.
|
223 | * ```js
|
224 | * supplier.account.update({balance: 100}, function(err, account) {
|
225 | * ...
|
226 | * });
|
227 | * ```
|
228 | *
|
229 | * Remove the account for the supplier.
|
230 | * ```js
|
231 | * supplier.account.destroy(function(err) {
|
232 | * ...
|
233 | * });
|
234 | * ```
|
235 | *
|
236 | * @param {Object|String} modelTo Model object (or String name of model) to which you are creating the relationship.
|
237 | * @options {Object} params Configuration parameters; see below.
|
238 | * @property {String} as Name of the property in the referring model that corresponds to the foreign key field in the related model.
|
239 | * @property {String} primaryKey Property name of primary key field.
|
240 | * @property {String} foreignKey Property name of foreign key field.
|
241 | * @property {String} polymorphic Define a polymorphic relation name.
|
242 | * @property {Object|Function} scope Explicitly define additional scopes.
|
243 | * @property {Object} model The model object.
|
244 | * @property {Object} properties Properties inherited from the parent object.
|
245 | * @property {Function} methods Scoped methods for the given relation.
|
246 | */
|
247 | RelationMixin.hasOne = function hasOne(modelTo, params) {
|
248 | return RelationDefinition.hasOne(this, modelTo, params);
|
249 | };
|
250 |
|
251 | /**
|
252 | * References one or more instances of the target model.
|
253 | *
|
254 | * For example, a Customer model references one or more instances of the Account model.
|
255 | *
|
256 | * Define the relation in the model definition:
|
257 | *
|
258 | * - Definition of Customer model:
|
259 | * ```json
|
260 | * {
|
261 | "name": "Customer",
|
262 | "base": "PersistedModel",
|
263 | "idInjection": true,
|
264 | "properties": {
|
265 | "name": {
|
266 | "type": "string"
|
267 | },
|
268 | "age": {
|
269 | "type": "number"
|
270 | }
|
271 | },
|
272 | "validations": [],
|
273 | "relations": {
|
274 | "accounts": {
|
275 | "type": "referencesMany",
|
276 | "model": "Account",
|
277 | "foreignKey": "accountIds",
|
278 | "options": {
|
279 | "validate": true,
|
280 | "forceId": false
|
281 | }
|
282 | }
|
283 | },
|
284 | "acls": [],
|
285 | "methods": {}
|
286 | }
|
287 | * ```
|
288 | *
|
289 | * - Definition of Account model:
|
290 | * ```json
|
291 | * {
|
292 | "name": "Account",
|
293 | "base": "PersistedModel",
|
294 | "idInjection": true,
|
295 | "properties": {
|
296 | "name": {
|
297 | "type": "string"
|
298 | },
|
299 | "balance": {
|
300 | "type": "number"
|
301 | }
|
302 | },
|
303 | "validations": [],
|
304 | "relations": {},
|
305 | "acls": [],
|
306 | "methods": {}
|
307 | }
|
308 | * ```
|
309 | *
|
310 | * On the bootscript, create a customer instance and for that customer instance reference many account instances.
|
311 | *
|
312 | * For example:
|
313 | * ```javascript
|
314 | * var Customer = app.models.Customer;
|
315 | var accounts = [
|
316 | {
|
317 | name: 'Checking',
|
318 | balance: 5000
|
319 | },
|
320 | {
|
321 | name: 'Saving',
|
322 | balance: 2000
|
323 | }
|
324 | ];
|
325 | Customer.create({name: 'Mary Smith'}, function(err, customer) {
|
326 | console.log('Customer:', customer);
|
327 | async.each(accounts, function(account, done) {
|
328 | customer.accounts.create(account, done);
|
329 | }, function(err) {
|
330 | console.log('Customer with accounts:', customer);
|
331 | customer.accounts(console.log);
|
332 | cb(err);
|
333 | });
|
334 | });
|
335 | * ```
|
336 | *
|
337 | * Sample referencesMany model data:
|
338 | * ```javascript
|
339 | * {
|
340 | id: 1,
|
341 | name: 'John Smith',
|
342 | accounts: [
|
343 | "saving-01", "checking-01",
|
344 | ]
|
345 | }
|
346 | * ```
|
347 | *
|
348 | * Supported helper methods:
|
349 | * - customer.accounts()
|
350 | * - customer.accounts.create()
|
351 | * - customer.accounts.build()
|
352 | * - customer.accounts.findById()
|
353 | * - customer.accounts.destroy()
|
354 | * - customer.accounts.updateById()
|
355 | * - customer.accounts.exists()
|
356 | * - customer.accounts.add()
|
357 | * - customer.accounts.remove()
|
358 | * - customer.accounts.at()
|
359 | *
|
360 | * @param {Object|String} modelTo Model object (or String name of model) to which you are creating the relationship.
|
361 | * @options {Object} params Configuration parameters; see below.
|
362 | * @property {String} as Name of the property in the referring model that corresponds to the foreign key field in the related model.
|
363 | * @property {Any} default The default value.
|
364 | * @property {Object} options Options to specify for the relationship.
|
365 | * @property {Boolean} options.forceId Force generation of id for embedded items. Default is false.
|
366 | * @property {Boolean} options.validate Denote if the embedded items should be validated. Default is true.
|
367 | * @property {Boolean} options.persistent Denote if the embedded items should be persisted. Default is false.
|
368 | * @property {Object|Function} scope Explicitly define additional scopes.
|
369 | * @property {String} foreignKey Property name of foreign key field.
|
370 | * @property {Object} properties Properties inherited from the parent object.
|
371 | * @property {Function} methods Scoped methods for the given relation.
|
372 | */
|
373 | RelationMixin.referencesMany = function referencesMany(modelTo, params) {
|
374 | return RelationDefinition.referencesMany(this, modelTo, params);
|
375 | };
|
376 |
|
377 | /**
|
378 | * Represent a model that embeds another model.
|
379 | *
|
380 | * For example, a Customer embeds one billingAddress from the Address model.
|
381 | *
|
382 | * - Define the relation in bootscript:
|
383 | * ```js
|
384 | * Customer.embedsOne(Address, {
|
385 | * as: 'address', // default to the relation name - address
|
386 | * property: 'billingAddress' // default to addressItem
|
387 | * });
|
388 | * ```
|
389 | *
|
390 | * OR, define the relation in the model definition:
|
391 | *
|
392 | * - Definition of Customer model:
|
393 | * ```json
|
394 | * {
|
395 | "name": "Customer",
|
396 | "base": "PersistedModel",
|
397 | "idInjection": true,
|
398 | "properties": {
|
399 | "name": {
|
400 | "type": "string"
|
401 | },
|
402 | "age": {
|
403 | "type": "number"
|
404 | }
|
405 | },
|
406 | "validations": [],
|
407 | "relations": {
|
408 | "address": {
|
409 | "type": "embedsOne",
|
410 | "model": "Address",
|
411 | "property": "billingAddress",
|
412 | "options": {
|
413 | "validate": true,
|
414 | "forceId": false
|
415 | }
|
416 | }
|
417 | },
|
418 | "acls": [],
|
419 | "methods": {}
|
420 | }
|
421 | * ```
|
422 | *
|
423 | * - Definition of Address model:
|
424 | * ```json
|
425 | * {
|
426 | "name": "Address",
|
427 | "base": "Model",
|
428 | "idInjection": true,
|
429 | "properties": {
|
430 | "street": {
|
431 | "type": "string"
|
432 | },
|
433 | "city": {
|
434 | "type": "string"
|
435 | },
|
436 | "state": {
|
437 | "type": "string"
|
438 | },
|
439 | "zipCode": {
|
440 | "type": "string"
|
441 | }
|
442 | },
|
443 | "validations": [],
|
444 | "relations": {},
|
445 | "acls": [],
|
446 | "methods": {}
|
447 | }
|
448 | * ```
|
449 | *
|
450 | * Sample embedded model data:
|
451 | * ```javascript
|
452 | * {
|
453 | id: 1,
|
454 | name: 'John Smith',
|
455 | billingAddress: {
|
456 | street: '123 Main St',
|
457 | city: 'San Jose',
|
458 | state: 'CA',
|
459 | zipCode: '95124'
|
460 | }
|
461 | }
|
462 | * ```
|
463 | *
|
464 | * Supported helper methods:
|
465 | * - customer.address()
|
466 | * - customer.address.build()
|
467 | * - customer.address.create()
|
468 | * - customer.address.update()
|
469 | * - customer.address.destroy()
|
470 | * - customer.address.value()
|
471 | *
|
472 | * @param {Object|String} modelTo Model object (or String name of model) to which you are creating the relationship.
|
473 | * @options {Object} params Configuration parameters; see below.
|
474 | * @property {String} as Name of the property in the referring model that corresponds to the foreign key field in the related model.
|
475 | * @property {String} property Name of the property for the embedded item.
|
476 | * @property {Any} default The default value.
|
477 | * @property {Object} options Options to specify for the relationship.
|
478 | * @property {Boolean} options.forceId Force generation of id for embedded items. Default is false.
|
479 | * @property {Boolean} options.validate Denote if the embedded items should be validated. Default is true.
|
480 | * @property {Boolean} options.persistent Denote if the embedded items should be persisted. Default is false.
|
481 | * @property {Object|Function} scope Explicitly define additional scopes.
|
482 | * @property {Object} properties Properties inherited from the parent object.
|
483 | * @property {Function} methods Scoped methods for the given relation.
|
484 | */
|
485 | RelationMixin.embedsOne = function embedsOne(modelTo, params) {
|
486 | return RelationDefinition.embedsOne(this, modelTo, params);
|
487 | };
|
488 |
|
489 | /**
|
490 | * Represent a model that can embed many instances of another model.
|
491 | *
|
492 | * For example, a Customer can have multiple email addresses and each email address is a complex object that contains label and address.
|
493 | *
|
494 | * Define the relation code in bootscript:
|
495 | * ```javascript
|
496 | Customer.embedsMany(EmailAddress, {
|
497 | as: 'emails', // default to the relation name - emailAddresses
|
498 | property: 'emailList' // default to emailAddressItems
|
499 | });
|
500 | * ```
|
501 | *
|
502 | * OR, define the relation in the model definition:
|
503 | *
|
504 | * - Definition of Customer model:
|
505 | * ```json
|
506 | * {
|
507 | "name": "Customer",
|
508 | "base": "PersistedModel",
|
509 | "idInjection": true,
|
510 | "properties": {
|
511 | "name": {
|
512 | "type": "string"
|
513 | },
|
514 | "age": {
|
515 | "type": "number"
|
516 | }
|
517 | },
|
518 | "validations": [],
|
519 | "relations": {
|
520 | "emails": {
|
521 | "type": "embedsMany",
|
522 | "model": "EmailAddress",
|
523 | "property": "emailList",
|
524 | "options": {
|
525 | "validate": true,
|
526 | "forceId": false
|
527 | }
|
528 | }
|
529 | },
|
530 | "acls": [],
|
531 | "methods": {}
|
532 | }
|
533 | * ```
|
534 | *
|
535 | * - Definition of EmailAddress model:
|
536 | * ```json
|
537 | * {
|
538 | "name": "EmailAddress",
|
539 | "base": "Model",
|
540 | "idInjection": true,
|
541 | "properties": {
|
542 | "label": {
|
543 | "type": "string"
|
544 | },
|
545 | "address": {
|
546 | "type": "string"
|
547 | }
|
548 | },
|
549 | "validations": [],
|
550 | "relations": {},
|
551 | "acls": [],
|
552 | "methods": {}
|
553 | }
|
554 | * ```
|
555 | *
|
556 | * Sample embedded model data:
|
557 | * ```javascript
|
558 | * {
|
559 | id: 1,
|
560 | name: 'John Smith',
|
561 | emails: [{
|
562 | label: 'work',
|
563 | address: 'john@xyz.com'
|
564 | }, {
|
565 | label: 'home',
|
566 | address: 'john@gmail.com'
|
567 | }]
|
568 | }
|
569 | * ```
|
570 | *
|
571 | * Supported helper methods:
|
572 | * - customer.emails()
|
573 | * - customer.emails.create()
|
574 | * - customer.emails.build()
|
575 | * - customer.emails.findById()
|
576 | * - customer.emails.destroyById()
|
577 | * - customer.emails.updateById()
|
578 | * - customer.emails.exists()
|
579 | * - customer.emails.add()
|
580 | * - customer.emails.remove()
|
581 | * - customer.emails.at()
|
582 | * - customer.emails.value()
|
583 | *
|
584 | * @param {Object|String} modelTo Model object (or String name of model) to which you are creating the relationship.
|
585 | * @options {Object} params Configuration parameters; see below.
|
586 | * @property {String} as Name of the property in the referring model that corresponds to the foreign key field in the related model.
|
587 | * @property {String} property Name of the property for the embedded item.
|
588 | * @property {Any} default The default value.
|
589 | * @property {Object} options Options to specify for the relationship.
|
590 | * @property {Boolean} options.forceId Force generation of id for embedded items. Default is false.
|
591 | * @property {Boolean} options.validate Denote if the embedded items should be validated. Default is true.
|
592 | * @property {Boolean} options.persistent Denote if the embedded items should be persisted. Default is false.
|
593 | * @property {String} polymorphic Define a polymorphic relation name.
|
594 | * @property {Object|Function} scope Explicitly define additional scopes.
|
595 | * @property {Object} properties Properties inherited from the parent object.
|
596 | * @property {Function} methods Scoped methods for the given relation.
|
597 | */
|
598 | RelationMixin.embedsMany = function embedsMany(modelTo, params) {
|
599 | return RelationDefinition.embedsMany(this, modelTo, params);
|
600 | };
|