# Enrich your models

{% hint style="warning" %}
Please be sure of your agent type and version and pick the right documentation accordingly.
{% endhint %}

{% tabs %}
{% tab title="Node.js" %}
{% hint style="danger" %}
This is the documentation of the `forest-express-sequelize` and `forest-express-mongoose` Node.js agents that will soon reach end-of-support.

`forest-express-sequelize` v9 and `forest-express-mongoose` v9 are replaced by [`@forestadmin/agent`](https://docs.forestadmin.com/developer-guide-agents-nodejs/) v1.

Please check your agent type and version and read on or switch to the right documentation.
{% endhint %}
{% endtab %}

{% tab title="Ruby on Rails" %}
{% hint style="success" %}
This is still the latest Ruby on Rails documentation of the `forest_liana` agent, you’re at the right place, please read on.
{% endhint %}
{% endtab %}

{% tab title="Python" %}
{% hint style="danger" %}
This is the documentation of the `django-forestadmin` Django agent that will soon reach end-of-support.

If you’re using a Django agent, notice that `django-forestadmin` v1 is replaced by [`forestadmin-agent-django`](https://docs.forestadmin.com/developer-guide-agents-python) v1.

If you’re using a Flask agent, go to the [`forestadmin-agent-flask`](https://docs.forestadmin.com/developer-guide-agents-python) v1 documentation.

Please check your agent type and version and read on or switch to the right documentation.
{% endhint %}
{% endtab %}

{% tab title="PHP" %}
{% hint style="danger" %}
This is the documentation of the `forestadmin/laravel-forestadmin` Laravel agent that will soon reach end-of-support.

If you’re using a Laravel agent, notice that `forestadmin/laravel-forestadmin` v1 is replaced by [`forestadmin/laravel-forestadmin`](https://docs.forestadmin.com/developer-guide-agents-php) v3.

If you’re using a Symfony agent, go to the [`forestadmin/symfony-forestadmin`](https://docs.forestadmin.com/developer-guide-agents-php) v1 documentation.

Please check your agent type and version and read on or switch to the right documentation.
{% endhint %}
{% endtab %}
{% endtabs %}

## Enrich your models

#### Declaring a new model

Whenever you have a new table/collection in your database, you will have to create file to declare it. Here is a **template example** for a `companies` table:

{% tabs %}
{% tab title="SQL" %}
{% code title="/models/companies.js" %}

```javascript
module.exports = (sequelize, DataTypes) => {
  const { Sequelize } = sequelize;
  const Company = sequelize.define('companies', {
    name: {
      type: DataTypes.STRING,
    },
    createdAt: {
      type: DataTypes.DATE,
    },
    ...
  }, {
    tableName: 'companies',
    underscored: true,
    schema: process.env.DATABASE_SCHEMA,
  });

  Company.associate = (models) => {
  };

  return Company;
};
```

{% endcode %}

**Fields** within that model should match your table's fields as shown in next section.

New **relationships** may be added there:

```javascript
Company.associate = (models) => {};
```

You can learn more about relationships on this [dedicated page](https://docs.forestadmin.com/documentation/reference-guide/models/relationships).
{% endtab %}

{% tab title="Mongodb" %}
{% code title="/models/companies.js" %}

```javascript
module.exports = (mongoose, Mongoose) => {
  const schema = Mongoose.Schema({
    'name': String,
    'createdAt': Date,
    ...
  }, {
    timestamps: false,
  });

  return mongoose.model('companies', schema, 'companies');
};
```

{% endcode %}

**Fields** within that model should match your collection's fields as shown in next section.

New **relationships** are to be added as properties:

```javascript
  'orders': [{ type: mongoose.Schema.Types.ObjectId, ref: 'orders' }],
  'customer_id': { type: mongoose.Schema.Types.ObjectId, ref: 'customers' },
```

You can learn more about relationships on this [dedicated page](https://docs.forestadmin.com/documentation/reference-guide/models/relationships).
{% endtab %}
{% endtabs %}

{% hint style="info" %}
When you manually add a new model, you need to configure the permissions for the corresponding collection in the UI (allow record details view, record creation, record edit, etc). By default a new collection is not visible and all permissions are disabled. You can set permissions by going to the [Roles settings](https://docs.forestadmin.com/user-guide/project-settings/teams-and-users/manage-roles).
{% endhint %}

#### Declaring a new field in a model

Any new field must be added **manually** within the corresponding model of your `/models` folder.

{% tabs %}
{% tab title="SQL" %}
Fields are declared as follows:

```javascript
createdAt: {
  type: DataTypes.DATE,
},
```

An exhaustive list of **DataTypes** can be found in [Sequelize documentation](https://sequelize.org/master/manual/data-types.html).

You can see how that snippet fits into your code in the [model example](#declaring-a-new-model) above.
{% endtab %}

{% tab title="Mongodb" %}
Fields are declared as follows:

```javascript
'createdAt': Date,
```

An exhaustive list of **SchemaTypes** can be found in [Mongoose documentation](https://mongoosejs.com/docs/schematypes.html#what-is-a-schematype).

You can see how that snippet fits into your code in the [model example](#declaring-a-new-model) above.
{% endtab %}
{% endtabs %}

#### Managing nested documents in Mongoose

{% hint style="info" %}
For a better user experience, you can [Flatten nested fields](https://docs.forestadmin.com/documentation/extra-help/setup/flatten-nested-fields-mongodb).
{% endhint %}

Lumber introspects your data structure recursively, so ***nested fields*** (object in object) are detected any level deep. Your **sub-documents** (array of nested fields) are detected as well.

{% hint style="warning" %}
Conflicting data types will result in the generation of a [mixed](https://mongoosejs.com/docs/schematypes.html#mixed) type field.
{% endhint %}

The following model...

```javascript
module.exports = (mongoose, Mongoose) => {
  const schema = Mongoose.Schema({
    // Level 0
    'age': Number,
    'id': Number,
    'name': String,
    // Level 1
    'address':{
      'addressDetail': String,
      'area': String,
      'city': String,
      'pincode': Number,
    },
    // Level 2
    'contactDetails':{
      'phone':{
        'homePhone': String,
        'mobilePhone': String,
      },
      'email': String,
    },
    // Related data
    'booksRead':[{
      'name': String,
      'authorName': String,
      'publishedBy': String,
    }],
  }, {
    timestamps: false,
  });
​
  return mongoose.model('testCollection', schema, 'testCollection');
};
```

...will result in the following interface:

![](https://2014605362-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fx9GURwd0RAwi8uWZgH5v%2Fuploads%2Fgit-blob-20b56f4b84487dc67c8ae5f3a0ddd8f37c807337%2Fnested-documents-field-customization-mongoose.png?alt=media)

#### Removing a model

By default **all** tables/collections in your database are analyzed by Lumber to generate your models. If you want to exclude some of them to prevent them from appearing in your Forest, check out [this how-to](https://docs.forestadmin.com/documentation/extra-help/settings/include-exclude-models).

#### Adding validation to your models

Validation allows you to keep control over your data's quality and integrity.

{% hint style="info" %}
If your existing app already has validation conditions, you may - or may not - want to reproduce the same validation conditions in your admin backend's models.

If so, you'll have to do it **manually**, using the below examples.
{% endhint %}

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

{% tabs %}
{% tab title="SQL" %}
In Sequelize, you add validation using the `validate` property:

{% code title="/models/customers.js" %}

```javascript
module.exports = (sequelize, DataTypes) => {
  const Customer = sequelize.define('customers', {
    ...
    'email': {
      type: DataTypes.STRING,
      validate: {
        isEmail: true,
        len: [10,25]
      }
    },
    ...
  },
  ...
  return Customer;
};
```

{% endcode %}

The 2 validators above will have the following effect on your email field:

![Invalid email](https://2014605362-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LR7SWfEwsNtj_ZiSkSA%2F-Lpm3-RXrgFiNKZgjCnk%2F-Lpm7EnVFgbOHv2u2gSU%2Fimage.png?alt=media\&token=52503ce8-f4c7-49dc-920c-1e6aabf1d528)

![Email too short (not within 10-25 range)](https://2014605362-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LR7SWfEwsNtj_ZiSkSA%2F-Lpm3-RXrgFiNKZgjCnk%2F-Lpm6r8bHkJF4ZF9Rzre%2Fimage.png?alt=media\&token=49f2c51a-5421-447d-a7a5-933807ee9227)

For an exhaustive list of available validators, check out the [Sequelize documentation](https://sequelize.org/master/manual/models-definition.html#validations).
{% endtab %}

{% tab title="Mongodb" %}
In Mongoose, you add validators alongside the `type` property:

{% code title="/models/customers.js" %}

```javascript
module.exports = (mongoose, Mongoose) => {
  const schema = Mongoose.Schema({
    'createdAt': Date,
    'email': {
      'type': String,
      'minlength': 10,
      'maxlength': 25
    },
    'firstname': String,
    ...
  }

  return mongoose.model('customer', schema, 'customer');
};
```

{% endcode %}

This is the effect on your field:

![Email is too short (not within 10-25 range)](https://2014605362-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LR7SWfEwsNtj_ZiSkSA%2F-LpmDG26D5mWRz5CBHSp%2F-LpmG_8AHTP7zeBvkt64%2Fimage.png?alt=media\&token=116f226a-2672-4ef8-bf7e-55a9e8174c32)

Mongoose has no build-in validators to check whether a string is an email. Should you want to validate that a content is an email, you have several solutions:

{% code title="/models/customers.js" %}

```javascript
module.exports = (mongoose, Mongoose) => {
  const schema = Mongoose.Schema({
    'createdAt': Date,
    'email': {
      'type': String,
      'match': [/^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/, 'Invalid email']
    },
    'firstname': String,
    ...
  }

  return mongoose.model('customer', schema, 'customer');
};
```

{% endcode %}

A better yet solution would be to rely on an external library called [validator.js](https://www.npmjs.com/package/validator) which provides many [build-in validators](https://www.npmjs.com/package/validator#validators):

{% code title="/models/customers.js" %}

```javascript
import { isEmail } from 'validator';

module.exports = (mongoose, Mongoose) => {
  const schema = Mongoose.Schema({
    'createdAt': Date,
    'email': {
      'type': String,
      'validate': [isEmail, 'Invalid email']
    },
    'firstname': String,
    ...
  }

  return mongoose.model('customer', schema, 'customer');
};
```

{% endcode %}

You then that any invalid email is refused:

![](https://2014605362-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LR7SWfEwsNtj_ZiSkSA%2F-LpmLtRjbX6t2erc1SWH%2F-LpmMg9NYjRs9bHAL44Y%2Fimage.png?alt=media\&token=ac01b16a-2814-4607-9eba-cbd4b1e61242)

For further details on validators in Mongoose, check out the [Mongoose documentation](https://mongoosejs.com/docs/validation.html#built-in-validators).
{% endtab %}
{% endtabs %}

####

#### Adding a default value to your models

You can choose to add a default value for some fields in your models. As a result, the corresponding fields will be prefilled with their default value in the creation form:

{% tabs %}
{% tab title="SQL" %}
{% code title="/models/customers.js" %}

```javascript
module.exports = (sequelize, DataTypes) => {
  const Customer = sequelize.define('customers', {
    ...
    'firstname': {
      'type': DataTypes.STRING,
      'defaultValue': 'Marc'
    },
    ...
  },
  ...
  return Customer;
};
```

{% endcode %}
{% endtab %}

{% tab title="Mongodb" %}
{% code title="/models/customers.js" %}

```javascript
module.exports = (mongoose, Mongoose) => {
  const schema = Mongoose.Schema({
    'createdAt': Date,
    'email': {
      'type': String,
      'default': 'Marc'
    },
    'firstname': String,
    ...
  }

  return mongoose.model('customer', schema, 'customer');
};
```

{% endcode %}
{% endtab %}
{% endtabs %}

![](https://2014605362-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LR7SWfEwsNtj_ZiSkSA%2F-LpmWKMTk8xJiQiA3O2A%2F-Lpma1GlYxpqzNn_5AOX%2Fimage.png?alt=media\&token=860c2809-6e93-4069-add5-19d01f2756df)

#### Adding a hook

Hooks are a powerful mechanism which allow you to automatically **trigger an event** at specific moments in your records lifecycle.

In our case, let's pretend we want to update a `update_count` field every time a record is updated:

{% tabs %}
{% tab title="SQL" %}
To add a `beforeSave` hook in Sequelize, use the following syntax:

{% code title="/models/orders.js" %}

```javascript
module.exports = (sequelize, DataTypes) => {
  var Order = sequelize.define('orders', {
    ...
    'update_count': {
      'type': DataTypes.INTEGER,
      'defaultValue': 0
    },
    ...
  },
  ...
  Order.beforeSave((order, options) => {
      order.update_count += 1;
    }
  );

  return Order;
};
```

{% endcode %}

Every time the order is updated, the updateCount field will be incremented by 1:

![](https://2014605362-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LR7SWfEwsNtj_ZiSkSA%2F-Lpmn61qGijh-OHKCHXK%2F-LpmqLQYOZIZKkvXQGAH%2FCapture%20d%E2%80%99e%CC%81cran%202019-09-27%20a%CC%80%2015.10.56.png?alt=media\&token=d68e2446-fe81-4595-88d8-68f427bdd3a6)

The exhaustive list of available hooks in Sequelize are available [here](https://sequelize.org/master/manual/hooks.html).
{% endtab %}

{% tab title="Mongodb" %}
To add a hook in Mongoose on `save` event, you may use the following snippet:

{% code title="/models/customers.js" %}

```javascript
module.exports = (mongoose, Mongoose) => {
  const schema = Mongoose.Schema({
    'update_count': {
      'type': Number,
      'default': 0
    },
    ...
  }

  schema.pre('save', async function() {
    const newCount = this.update_count + 1;
    const incrementCount = () => {
      this.set('update_count', newCount);
    };
    await incrementCount();
  });

  return mongoose.model('order', schema, 'order');
};
```

{% endcode %}

{% hint style="warning" %}
As mentioned in [their documentation](https://mongoosejs.com/docs/middleware.html#notes)

*Pre and post `save()` hooks are **not** executed on `update()`, `findOneAndUpdate()`, etc.*

This would only work if you specifically call `save` in your update method.
{% endhint %}
{% endtab %}
{% endtabs %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.forestadmin.com/documentation/reference-guide/models/enrich-your-models.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
