Skip to content

Relations

Radosław Mejer edited this page May 19, 2020 · 5 revisions

Before we start

The relations plugin is heavily inspired by Laravel Eloquent. Kex borrows the naming conventions, database schema, and event argument names from Eloquent. If some concepts in this wiki page are not clear enough it's possible that the Eloquent docs will clear them up to you.

TOC

Declaring relations

const { 
  HasMany, 
  HasOne, 
  BelongsTo, 
  BelongsToMany 
} = require('@baethon/kex')

const User = kex.createModel('User', {
  relations: {
    messages: new HasMany('Message'),
    settings: new HasOne('Setting'),
    team: new BelongsTo('Team'),
    tags: new BelongsToMany('Tag')
  }
})

Querying relations

The relations plugin allows fetching the related records with the main query.

const usersList = await User.query()
  .include('team')

/**
[
  {
    id: 1,
    team: {
      id: 2,
      name: 'The Team'
    }
  }
]
*/

When using include(), kex will generate N+1 queries (where N is the number of included relations).

Fetching related items for a single row

Each relation will have in Model a query builder factory method. It creates a query builder, which allows fetching the related data.

const team = await User.team(userId)
const tags = await User.tags(userId)

Nested relations

You can include() the relations of relation.

const usersList = await User.query()
  .include('team.organization')

/**
[
  {
    id: 1,
    team: {
      id: 2,
      name: 'The Team',
      organization: { id: 1, name: 'ACME' }
    }
  }
]
*/

Modifying the include query

In general, the include() method will fetch all of the related records. When the related model has some global scopes, they will be applied.

It's possible to alter the query for the related model during the query:

const usersList = await User.query()
  .include({
    messages: qb => {
      // qb is instanceof Message model
      qb.recent() // <-- use the recent() scope method
    }
  })

Supported relations

HasOne

One-to-one relation where the base model is the "parent" for the related model.

const { HasOne } = require('@baethon/kex')

const User = kex.createModel('User', {
  relations: {
    settings: new HasOne('Setting', /** options */)
  }
})

Example table schema:

+-----------------+           +----------------------+
| users           |           | settings             |
+-----------------+           +----------------------+
| id (localKey)   | <---      | id                   |
|                 |     \---- | user_id (foreignKey) |
+-----------------+           +----------------------+

The HasOne relation supports the following options:

  • foreignKey (optional; String)
  • localKey (optional; String; default: id)

HasMany

One-to-many relation where the base model is the "parent" for the related models.

const { HasMany } = require('@baethon/kex')

const User = kex.createModel('User', {
  relations: {
    messages: new HasMany('Message', /** options */)
  }
})

Example table schema:

+---------------+           +----------------------+
|     users     |           |       messages       |
+---------------+           +----------------------+
| id (localKey) | <---      | id                   |
|               |     \---- | user_id (foreignKey) |
+---------------+           +----------------------+

The HasMany relation supports the following options:

  • foreignKey (optional; String)
  • localKey (optional; String; default: id)

BelongsTo

One-to-one relation where the base model is a "child" of the related model.

const { BelongsTo } = require('@baethon/kex')

const User = kex.createModel('User', {
  relations: {
    settings: new BelongsTo('Organization', /** options */)
  }
})

Example table schema:

+---------------+           +------------------------------+
| organization  |           |            users             |
+---------------+           +------------------------------+
| id (otherKey) | <---      | id                           |
|               |     \---- | organization_id (foreignKey) |
+---------------+           +------------------------------+

The BelongsTo relation supports the following options:

  • otherKey (optional; String; default: id)
  • foreignKey (optional; String)

BelongsToMany

Many-to-many relation which requires a pivot table to associate the models.

const { BelongsToMany } = require('@baethon/kex')

const User = kex.createModel('User', {
  relations: {
    tags: new BelongsToMany('Tag', /** options **/)
  }
})

Example table schema:

+-----------------+      +---------------------------+      +----------------+
|      tags       |      |     tag_user (table)      |      |     users      |
+-----------------+      +---------------------------+      +----------------+
| id (relatedKey) | <-   | user_id (foreignPivotKey) | ---> | id (parentKey) |
|                 |   \- | tag_id (relatedPivotKey)  |      |                |
+-----------------+      +---------------------------+      +----------------+

The BelongsToMany relation supports the following options:

  • table (optional; String) the name of the pivot table; by default it's a snake_case version of models table names (in singular form) joined in alphabetical order
  • foreignPivotKey (optional; String)
  • relatedPivotKey (optional; String)
  • parentKey (optional; String)
  • relatedKey (optional; String)