UNPKG

19 kBJavaScriptView Raw
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'use strict';
7
8/*!
9 * Dependencies
10 */
11const relation = require('./relation-definition');
12const RelationDefinition = relation.RelationDefinition;
13
14module.exports = RelationMixin;
15
16/**
17 * RelationMixin class. Use to define relationships between models.
18 *
19 * @class RelationMixin
20 */
21function 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 */
82RelationMixin.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 */
142RelationMixin.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 */
181RelationMixin.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 */
247RelationMixin.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 */
373RelationMixin.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 */
485RelationMixin.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 */
598RelationMixin.embedsMany = function embedsMany(modelTo, params) {
599 return RelationDefinition.embedsMany(this, modelTo, params);
600};