// Copyright IBM Corp. 2013,2019. All Rights Reserved. // Node module: loopback-datasource-juggler // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT 'use strict'; /*! * Dependencies */ const relation = require('./relation-definition'); const RelationDefinition = relation.RelationDefinition; module.exports = RelationMixin; /** * RelationMixin class. Use to define relationships between models. * * @class RelationMixin */ function RelationMixin() { } /** * Define a "one to many" relationship by specifying the model name. * * Examples: * ``` * User.hasMany(Post, {as: 'posts', foreignKey: 'authorId'}); * ``` * * ``` * Book.hasMany(Chapter); * ``` * Or, equivalently: * ``` * Book.hasMany('chapters', {model: Chapter}); * ``` * * Query and create related models: * * ```js * Book.create(function(err, book) { * * // Create a chapter instance ready to be saved in the data source. * var chapter = book.chapters.build({name: 'Chapter 1'}); * * // Save the new chapter * chapter.save(); * * // you can also call the Chapter.create method with the `chapters` property which will build a chapter * // instance and save the it in the data source. * book.chapters.create({name: 'Chapter 2'}, function(err, savedChapter) { * // this callback is optional * }); * * // Query chapters for the book * book.chapters(function(err, chapters) { * // all chapters with bookId = book.id * console.log(chapters); * }); * * // Query chapters for the book with a filter * book.chapters({where: {name: 'test'}, function(err, chapters) { * // All chapters with bookId = book.id and name = 'test' * console.log(chapters); * }); * }); * ``` * * @param {Object|String} modelTo Model object (or String name of model) to which you are creating the relationship. * @options {Object} params Configuration parameters; see below. * @property {String} as Name of the property in the referring model that corresponds to the foreign key field in the related model. * @property {String} foreignKey Property name of foreign key field. * @property {String} polymorphic Define a polymorphic relation name. * @property {String} through Name of the through model. * @property {String} keyThrough Property name of the foreign key in the through model. * @property {Object|Function} scope Explicitly define additional scopes. * @property {Boolean} invert Specify if the relation is inverted. * @property {Object} model The model object. */ RelationMixin.hasMany = function hasMany(modelTo, params) { return RelationDefinition.hasMany(this, modelTo, params); }; /** * Declare "belongsTo" relation that sets up a one-to-one connection with another model, such that each * instance of the declaring model "belongs to" one instance of the other model. * * For example, if an application includes users and posts, and each post can be written by exactly one user. * The following code specifies that `Post` has a reference called `author` to the `User` model via the `userId` property of `Post` * as the foreign key. * ``` * Post.belongsTo(User, {as: 'author', foreignKey: 'userId'}); * ``` * You can then access the author in one of the following styles. * Get the User object for the post author asynchronously: * ``` * post.author(callback); * ``` * Get the User object for the post author synchronously: * ``` * post.author(); * ``` * Set the author to be the given user: * ``` * post.author(user) * ``` * Examples: * * Suppose the model Post has a *belongsTo* relationship with User (the author of the post). You could declare it this way: * ```js * Post.belongsTo(User, {as: 'author', foreignKey: 'userId'}); * ``` * * When a post is loaded, you can load the related author with: * ```js * post.author(function(err, user) { * // the user variable is your user object * }); * ``` * * The related object is cached, so if later you try to get again the author, no additional request will be made. * But there is an optional boolean parameter in first position that set whether or not you want to reload the cache: * ```js * post.author(true, function(err, user) { * // The user is reloaded, even if it was already cached. * }); * ``` * This optional parameter default value is false, so the related object will be loaded from cache if available. * * @param {Class|String} modelTo Model object (or String name of model) to which you are creating the relationship. * @options {Object} params Configuration parameters; see below. * @property {String} as Name of the property in the referring model that corresponds to the foreign key field in the related model. * @property {String} primaryKey Property name of primary key field. * @property {String} foreignKey Name of foreign key property. * @property {Object|Function} scope Explicitly define additional scopes. * @property {Object} properties Properties inherited from the parent object. * @property {Object} options Property level options. * @property {Boolean} options.invertProperties Specify if the properties should be inverted. */ RelationMixin.belongsTo = function(modelTo, params) { return RelationDefinition.belongsTo(this, modelTo, params); }; /** * A hasAndBelongsToMany relation creates a direct many-to-many connection with another model, with no intervening model. * * For example, if your application includes users and groups, with each group having many users and each user appearing * in many groups, you could declare the models this way: * ``` * User.hasAndBelongsToMany('groups', {model: Group, foreignKey: 'groupId'}); * ``` * Then, to get the groups to which the user belongs: * ``` * user.groups(callback); * ``` * Create a new group and connect it with the user: * ``` * user.groups.create(data, callback); * ``` * Connect an existing group with the user: * ``` * user.groups.add(group, callback); * ``` * Remove the user from the group: * ``` * user.groups.remove(group, callback); * ``` * * @param {String|Object} modelTo Model object (or String name of model) to which you are creating the relationship. * @options {Object} params Configuration parameters; see below. * @property {String} as Name of the property in the referring model that corresponds to the foreign key field in the related model. * @property {String} foreignKey Property name of foreign key field. * @property {String} throughTable The table name of the through model. * @property {String} through Name of the through model. * @property {String} polymorphic Define a polymorphic relation name. * @property {Object|Function} scope Explicitly define additional scopes. * @property {Object} model The model object. */ RelationMixin.hasAndBelongsToMany = function hasAndBelongsToMany(modelTo, params) { return RelationDefinition.hasAndBelongsToMany(this, modelTo, params); }; /** * Define a "one to one" relationship by specifying the model name. * * Examples: * ``` * Supplier.hasOne(Account, {as: 'account', foreignKey: 'supplierId'}); * ``` * * If the target model doesn’t have a foreign key property, LoopBack will add a property with the same name. * * The type of the property will be the same as the type of the target model’s id property. * * Please note the foreign key property is defined on the target model (in this example, Account). * * If you don’t specify them, then LoopBack derives the relation name and foreign key as follows: * - Relation name: Camel case of the model name, for example, for the “supplier” model the relation is “supplier”. * - Foreign key: The relation name appended with Id, for example, for relation name “supplier” the default foreign key is “supplierId”. * * Build a new account for the supplier with the supplierId to be set to the id of the supplier. * ```js * var supplier = supplier.account.build(data); * ``` * * Create a new account for the supplier. If there is already an account, an error will be reported. * ```js * supplier.account.create(data, function(err, account) { * ... * }); * ``` * * Find the supplier's account model. * ```js * supplier.account(function(err, account) { * ... * }); * ``` * * Update the associated account. * ```js * supplier.account.update({balance: 100}, function(err, account) { * ... * }); * ``` * * Remove the account for the supplier. * ```js * supplier.account.destroy(function(err) { * ... * }); * ``` * * @param {Object|String} modelTo Model object (or String name of model) to which you are creating the relationship. * @options {Object} params Configuration parameters; see below. * @property {String} as Name of the property in the referring model that corresponds to the foreign key field in the related model. * @property {String} primaryKey Property name of primary key field. * @property {String} foreignKey Property name of foreign key field. * @property {String} polymorphic Define a polymorphic relation name. * @property {Object|Function} scope Explicitly define additional scopes. * @property {Object} model The model object. * @property {Object} properties Properties inherited from the parent object. * @property {Function} methods Scoped methods for the given relation. */ RelationMixin.hasOne = function hasOne(modelTo, params) { return RelationDefinition.hasOne(this, modelTo, params); }; /** * References one or more instances of the target model. * * For example, a Customer model references one or more instances of the Account model. * * Define the relation in the model definition: * * - Definition of Customer model: * ```json * { "name": "Customer", "base": "PersistedModel", "idInjection": true, "properties": { "name": { "type": "string" }, "age": { "type": "number" } }, "validations": [], "relations": { "accounts": { "type": "referencesMany", "model": "Account", "foreignKey": "accountIds", "options": { "validate": true, "forceId": false } } }, "acls": [], "methods": {} } * ``` * * - Definition of Account model: * ```json * { "name": "Account", "base": "PersistedModel", "idInjection": true, "properties": { "name": { "type": "string" }, "balance": { "type": "number" } }, "validations": [], "relations": {}, "acls": [], "methods": {} } * ``` * * On the bootscript, create a customer instance and for that customer instance reference many account instances. * * For example: * ```javascript * var Customer = app.models.Customer; var accounts = [ { name: 'Checking', balance: 5000 }, { name: 'Saving', balance: 2000 } ]; Customer.create({name: 'Mary Smith'}, function(err, customer) { console.log('Customer:', customer); async.each(accounts, function(account, done) { customer.accounts.create(account, done); }, function(err) { console.log('Customer with accounts:', customer); customer.accounts(console.log); cb(err); }); }); * ``` * * Sample referencesMany model data: * ```javascript * { id: 1, name: 'John Smith', accounts: [ "saving-01", "checking-01", ] } * ``` * * Supported helper methods: * - customer.accounts() * - customer.accounts.create() * - customer.accounts.build() * - customer.accounts.findById() * - customer.accounts.destroy() * - customer.accounts.updateById() * - customer.accounts.exists() * - customer.accounts.add() * - customer.accounts.remove() * - customer.accounts.at() * * @param {Object|String} modelTo Model object (or String name of model) to which you are creating the relationship. * @options {Object} params Configuration parameters; see below. * @property {String} as Name of the property in the referring model that corresponds to the foreign key field in the related model. * @property {Any} default The default value. * @property {Object} options Options to specify for the relationship. * @property {Boolean} options.forceId Force generation of id for embedded items. Default is false. * @property {Boolean} options.validate Denote if the embedded items should be validated. Default is true. * @property {Boolean} options.persistent Denote if the embedded items should be persisted. Default is false. * @property {Object|Function} scope Explicitly define additional scopes. * @property {String} foreignKey Property name of foreign key field. * @property {Object} properties Properties inherited from the parent object. * @property {Function} methods Scoped methods for the given relation. */ RelationMixin.referencesMany = function referencesMany(modelTo, params) { return RelationDefinition.referencesMany(this, modelTo, params); }; /** * Represent a model that embeds another model. * * For example, a Customer embeds one billingAddress from the Address model. * * - Define the relation in bootscript: * ```js * Customer.embedsOne(Address, { * as: 'address', // default to the relation name - address * property: 'billingAddress' // default to addressItem * }); * ``` * * OR, define the relation in the model definition: * * - Definition of Customer model: * ```json * { "name": "Customer", "base": "PersistedModel", "idInjection": true, "properties": { "name": { "type": "string" }, "age": { "type": "number" } }, "validations": [], "relations": { "address": { "type": "embedsOne", "model": "Address", "property": "billingAddress", "options": { "validate": true, "forceId": false } } }, "acls": [], "methods": {} } * ``` * * - Definition of Address model: * ```json * { "name": "Address", "base": "Model", "idInjection": true, "properties": { "street": { "type": "string" }, "city": { "type": "string" }, "state": { "type": "string" }, "zipCode": { "type": "string" } }, "validations": [], "relations": {}, "acls": [], "methods": {} } * ``` * * Sample embedded model data: * ```javascript * { id: 1, name: 'John Smith', billingAddress: { street: '123 Main St', city: 'San Jose', state: 'CA', zipCode: '95124' } } * ``` * * Supported helper methods: * - customer.address() * - customer.address.build() * - customer.address.create() * - customer.address.update() * - customer.address.destroy() * - customer.address.value() * * @param {Object|String} modelTo Model object (or String name of model) to which you are creating the relationship. * @options {Object} params Configuration parameters; see below. * @property {String} as Name of the property in the referring model that corresponds to the foreign key field in the related model. * @property {String} property Name of the property for the embedded item. * @property {Any} default The default value. * @property {Object} options Options to specify for the relationship. * @property {Boolean} options.forceId Force generation of id for embedded items. Default is false. * @property {Boolean} options.validate Denote if the embedded items should be validated. Default is true. * @property {Boolean} options.persistent Denote if the embedded items should be persisted. Default is false. * @property {Object|Function} scope Explicitly define additional scopes. * @property {Object} properties Properties inherited from the parent object. * @property {Function} methods Scoped methods for the given relation. */ RelationMixin.embedsOne = function embedsOne(modelTo, params) { return RelationDefinition.embedsOne(this, modelTo, params); }; /** * Represent a model that can embed many instances of another model. * * For example, a Customer can have multiple email addresses and each email address is a complex object that contains label and address. * * Define the relation code in bootscript: * ```javascript Customer.embedsMany(EmailAddress, { as: 'emails', // default to the relation name - emailAddresses property: 'emailList' // default to emailAddressItems }); * ``` * * OR, define the relation in the model definition: * * - Definition of Customer model: * ```json * { "name": "Customer", "base": "PersistedModel", "idInjection": true, "properties": { "name": { "type": "string" }, "age": { "type": "number" } }, "validations": [], "relations": { "emails": { "type": "embedsMany", "model": "EmailAddress", "property": "emailList", "options": { "validate": true, "forceId": false } } }, "acls": [], "methods": {} } * ``` * * - Definition of EmailAddress model: * ```json * { "name": "EmailAddress", "base": "Model", "idInjection": true, "properties": { "label": { "type": "string" }, "address": { "type": "string" } }, "validations": [], "relations": {}, "acls": [], "methods": {} } * ``` * * Sample embedded model data: * ```javascript * { id: 1, name: 'John Smith', emails: [{ label: 'work', address: 'john@xyz.com' }, { label: 'home', address: 'john@gmail.com' }] } * ``` * * Supported helper methods: * - customer.emails() * - customer.emails.create() * - customer.emails.build() * - customer.emails.findById() * - customer.emails.destroyById() * - customer.emails.updateById() * - customer.emails.exists() * - customer.emails.add() * - customer.emails.remove() * - customer.emails.at() * - customer.emails.value() * * @param {Object|String} modelTo Model object (or String name of model) to which you are creating the relationship. * @options {Object} params Configuration parameters; see below. * @property {String} as Name of the property in the referring model that corresponds to the foreign key field in the related model. * @property {String} property Name of the property for the embedded item. * @property {Any} default The default value. * @property {Object} options Options to specify for the relationship. * @property {Boolean} options.forceId Force generation of id for embedded items. Default is false. * @property {Boolean} options.validate Denote if the embedded items should be validated. Default is true. * @property {Boolean} options.persistent Denote if the embedded items should be persisted. Default is false. * @property {String} polymorphic Define a polymorphic relation name. * @property {Object|Function} scope Explicitly define additional scopes. * @property {Object} properties Properties inherited from the parent object. * @property {Function} methods Scoped methods for the given relation. */ RelationMixin.embedsMany = function embedsMany(modelTo, params) { return RelationDefinition.embedsMany(this, modelTo, params); };