LoopBack supports several kinds of embedded relations: embedsOne, embedsMany, embedsMany with belongsTo, and referencesMany.
Page Contents

Overview

LoopBack relations enable you to create connections between models and provide navigation/aggregation APIs to deal with a graph of model instances. In addition to the traditional ones, LoopBack also supports the following embedded relations:

  • EmbedsOne - a model that embeds another model; for example, a Customer embeds one billingAddress.
  • EmbedsMany - a model that embeds 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..
  • EmbedsMany with belongsTo - a model that embeds many links to related people, such as an author or a reader. 
  • ReferencesMany

EmbedsOne

EmbedsOne is used to represent a model that embeds another model, for example, a Customer embeds one billingAddress.

Sample embedded model

{
  id: 1,
  name: 'John Smith',
  billingAddress: {
    street: '123 Main St',
    city: 'San Jose',
    state: 'CA',
    zipCode: '95124'
  }
}

Define the relation in code

common/models/customer.js

Customer.embedsOne(Address, {
  as: 'address', // default to the relation name - address
  property: 'billingAddress' // default to addressItem
});

Parameters for the definition

  • methods - Scoped methods for the given relation
  • properties - Properties taken from the parent object
  • scope - Default scope
  • options - Options
  • default - Default value
  • property - Name of the property for the embedded item
  • as - Name of the relation

Options

  • forceId - force generation of ida for embedded items, default to false
  • validate - denote if the embedded items should be validated, default to true
  • persistent - denote if the embedded items should be persisted, default to false

Define the relation in JSON

common/models/customer.json

{
  "name": "Customer",
  "base": "PersistedModel",
  "idInjection": true,
  "properties": {
    "name": {
      "type": "string"
    }
  },
  "relations": {
    "address": {
      "type": "embedsOne",
      "model": "Address",
      "property": "billingAddress",
      "options": {
        "validate": true,
        "forceId": false
      }
    }
    ...
  }

Helper methods

  • customer.address()
  • customer.address.build()
  • customer.address.create()
  • customer.address.update()
  • customer.address.destroy()
  • customer.address.value()

For examples of these in use, see boot scripts in loopback-example-relations.

Operation hooks

You can define before save and after save operation hooks for an embedded model in an embedsOne relation. Then, updating or creating an instance of the container model will trigger the operation hook on the embedded model. When this occurs, ctx.isNewInstance is false, because only a new instance of the container model is created.

For example, if Customer embedsOne Address, and you define a before save hook on the Address model, creating a new Customer instance will trigger the operation hook.

EmbedsMany

Use an embedsMany relation to indicate that a model 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.

Sample model instance with many embedded models

{
  id: 1,
  name: 'John Smith',
  emails: [{
    label: 'work',
    address: '[email protected]'
  }, {
    label: 'home',
    address: '[email protected]'
  }]
}

Define the relation in code

common/models/customer.js

Customer.embedsOne(EmailAddress, {
  as: 'emails', // default to the relation name - emailAddresses
  property: 'emailList' // default to emailAddressItems
});

Parameters for the definition

  • methods
  • properties
  • scope
  • options
  • default
  • property
  • as

Options

  • forceId
  • validate
  • persistent

Define the relation in JSON

common/models/customer.json

{
  "name": "Customer",
  "base": "PersistedModel",
  "idInjection": true,
  "properties": {
    "name": {
      "type": "string"
    }
  },
  "relations": {
    "emails": {
      "type": "embedsMany",
      "model": "EmailAddress",
      "property": "emailList",
      "options": {
        "validate": true,
        "forceId": false
      }
    }
    ...
  }

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.get() - alias to findById
  • customer.emails.set() - alias to updateById
  • customer.emails.unset() - alias to destroyById
  • customer.emails.at()
  • customer.emails.value()

Operation hooks

You can define before save and after save operation hooks for an embedded model in an embedsMany relation. Then, updating or creating an instance of the container model will trigger the operation hook on the embedded model. When this occurs, ctx.isNewInstance is false, because only a new instance of the container model is created.

For example, if Customer embedsOne Address, and you define a before save hook on the Address model, creating a new Customer instance will trigger the operation hook.

EmbedsMany with belongsTo

Use an embedsMany with belongsTo relation to indicate a model that can embed many links to other models; for example a book model that embeds many links to related people, such as an author or a reader. Each link belongs to a person and it’s polymorphic, since a person can be an Author or a Reader.

Sample embedsMany with belongsTo model instance

{ 
  id: 1
  name: 'Book 1',
  links: [{
    notes: 'Note 1',
    id: 1,
    linkedId: 1,
    linkedType: 'Author',
    name: 'Author 1'
  }, {
  notes: 'Note 2',
    id: 2,
    linkedId: 1,
    linkedType: 'Reader',
    name: 'Reader 1'
  }]
}

Define the embedsMany relation for Book

common/models/book.json

{
  "name": "Book",
  "base": "PersistedModel",
  "idInjection": true,
  "properties": {
    "name": {
      "type": "string"
    }
  },
  "validations": [],
  "relations": {
    "people": {
      "type": "embedsMany",
      "model": "Link",
      "scope": {
        "include": "linked"
      }
    }
  },
  "acls": [],
  "methods": []
}

common/models/link.json

{
  "name": "Link",
  "base": "Model",
  "idInjection": true,
  "properties": {
    "id": {
      "type": "number",
      "id": true
    },
    "name": {
      "type": "string"
    },
    "notes": {
      "type": "string"
    }
  },
  "validations": [],
  "relations": {
    "linked": {
      "type": "belongsTo",
      "polymorphic": {
        "idType": "number"
      },
      "properties": {
        "name": "name"
      },
      "options": {
        "invertProperties": true
      }
    }
  },
  "acls": [],
  "methods": []
}

ReferencesMany

Sample referencesMany model instance

{
  id: 1,
  name: 'John Smith',
  accounts: [
    "saving-01", "checking-01",
  ]
}

Parameters for the definition

  • methods
  • properties
  • foreignKey
  • scope
  • options
  • default
  • as

Options

  • forceId
  • validate
  • persistent

Define the relation in code

common/models/customer.json

{
  "name": "Customer",
  "base": "PersistedModel",
  "idInjection": true,
  "properties": {
    "name": {
      "type": "string"
    }
  },
  "relations": {
    "accounts": {
      "type": "referencesMany",
      "model": "Account",
      "foreignKey": "accountIds",
      "options": {
        "validate": true,
        "forceId": false
      }
    }
...
}

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()

Transient versus persistent for the embedded model

Define a transient data source

server/datasources.json

{
  ...
  "transient": {
    "name": "transient",
    "connector": "transient"
  }
}

Use the transient data source for embedded models

server/model-config.json

{
  ...
  "Customer": {
    "dataSource": "db",
    "public": true
  },
  "Address": {
    "dataSource": "transient",
    "public": false
  },
  "EmailAddress": {
    "dataSource": "transient",
    "public": false
  },
  "Account": {
    "dataSource": "db",
    "public": false
  }
}

The embedded instances are stored inside their parent instance, which means you can see them in parentModel’s database record. The default idType for a embedded model is “String”, not the same type of its parent model id.

Tags: models