Relationships

What is a relationship?

A relationship is a connection between two collections.

Relationships are visible and actionable in Forest Admin:

  • hasMany (1)

  • belongsTo or hasOne(2)

Note that Lumber automatically generates most relationships. However, depending on your database nature and structure, you may have add some manually.

Adding relationships

Depending on your database type, Lumber will have generated models in Sequelize (for SQL databases) or Mongoose (for Mongo databases).

Below are some simple snippets showing you how to add relationships. However, should you want to dig deeper, please refer to the appropriate framework's documentations:

Adding a hasMany relationship

In our Live demo, a customer can have multiple orders. In that case, we have to use a hasMany relationship.

SQL
Mongodb
SQL
/models/customers.js
module.exports = (sequelize, DataTypes) => {
const Customer = sequelize.define('customers',
...
);
Customer.associate = (models) => {
Customer.hasMany(models.orders);
};
return Customer;
};
Mongodb
/models/customers.js
const mongoose = require('mongoose');
const schema = mongoose.Schema({
...
'orders': [{ type: mongoose.Schema.Types.ObjectId, ref: 'orders' }],
...
}, {
timestamps: true,
});
module.exports = mongoose.model('customers', schema, 'customers');

Note that for orders to be displayed within the related data section of your customer, they have to be populated in your database. For instance:

Once you've added your relationship(s) in your model(s), they will only be taken into account after you restart your server.

Adding a hasOne relationship

In case of a one-to-one relationship between 2 collections, the opposite of a belongsTo relationship is a hasOne relationship. Taking the same example as before, the opposite of "an address belongsTo a customer" is simply "a customerhasOne address".

SQL
Mongodb
SQL
/models/customers.js
module.exports = (sequelize, DataTypes) => {
const Customer = sequelize.define('customers',
...
);
Customer.associate = (models) => {
Customer.hasOne(models.addresses);
};
return Customer;
};
Mongodb
/models/customers.js
const mongoose = require('mongoose');
const schema = mongoose.Schema({
...
'address': { type: mongoose.Schema.Types.ObjectId, ref: 'addresses' },
...
}, {
timestamps: true,
});
module.exports = mongoose.model('customers', schema, 'customers');

Don't forget to restart your server for your newly added relationships to be taken into account.

Adding a belongsTo relationship

On our Live Demo example, the Address model has a foreignKey customer_id that points to the Customer. In other words, an addressbelongsTo a customer.

SQL
Mongodb
SQL
/models/addresses.js
module.exports = (sequelize, DataTypes) => {
const Address = sequelize.define('addresses',
...
);
Address.associate = (models) => {
Address.belongsTo(models.customers);
};
return Address;
};
Mongodb
/models/addresses.js
const mongoose = require('mongoose');
const schema = mongoose.Schema({
...
'customer_id': { type: mongoose.Schema.Types.ObjectId, ref: 'customers' },
...
}, {
timestamps: true,
});
module.exports = mongoose.model('addresses', schema, 'addresses');

This will work if your foreign keys are correctly named: For a collection collectionName, the foreign key should be collection_name_id. If this is not the case, check out the section below.

Don't forget to restart your server for your newly added relationships to be taken into account.

Declaring a foreign key (SQL only)

It's possible that your tables are linked in an unusual way (using names instead of ids for instance). In that case, adding the above code will not suffice to add the belongsTo relationship. Even though we recommend you modify your database structure to stay within foreign key conventions (pointing to an id), there is a way to specify how your tables are linked.

If the field fk_customername of a table Address points to the field name of a table Customer, add the following:

/models/addresses.js
...
Address.associate = (models) => {
Address.belongsTo(models.customers, {
foreignKey: 'fk_companyname'
targetKey: 'name'
});
};
...

This is explained in Sequelize's documentation.

Adding a belongsToMany relationship (SQL only)

belongsToMany association is often used to set up a many-to-many relationship with another model. For this example, we will consider the models Projects and Users. A user can be part of many projects, and one project has many users. The junction table that will keep track of the associations will be called userProjects, which will contain the foreign keys projectId and userId.

/models/user-projects.js
...
UserProjects.associate = (models) => {
UserProjects.belongsTo(models.projects, {
foreignKey: {
name: 'projectIdKey',
field: 'projectId',
},
as: 'project',
});
UserProjects.belongsTo(models.users, {
foreignKey: {
name: 'userIdKey',
field: 'userId',
},
as: 'user',
});
};
...
/models/users.js
...
Users.associate = (models) => {
Users.belongsToMany(models.projects, {
through: 'userProjects',
foreignKey: 'userId',
otherKey: 'projectId',
});
};
...
/models/projects.js
...
Projects.associate = (models) => {
Projects.belongsToMany(models.users, {
through: 'userProjects',
foreignKey: 'projectId',
otherKey: 'userId',
});
};
...

Lumber relationship generation rules

Lumber automatically generates most relationships, according to the below rules:

SQL
Mongodb
SQL

BelongsTo

Detecting belongsTo is straight forward, we check if the referenced table of the foreign key is unique (unique constraint or primary key), then a belongsTo association can be set between the two tables.

HasMany

If the foreign key doesn't have a unicity constraint, then we can define a hasMany association.

HasOne

If the foreign key also have a unique constraint or is used as the primary key of its table, then we can define a hasOne association.

BelongsToMany

We detect Many-to-Many relationships when we detect a simple junction table. We are able to detect a junction table when it contains 2 foreign keys. It can optionally contain additional fields like a primary key and technical timestamps.

Mongodb

BelongsTo

When a document contains an ObjectID refering to another document, we create a belongsTo relationship to the corresponding collection.

HasMany

When a document contains an array of ObjectIDs refering to other documents, we create a hasMany relationship to the corresponding collection.

HasOne

Not automatically generated.

BelongsToMany

Not automatically generated.