Nodea applications are generated with a database which can be of MySQL, MariaDB or even PostgreSQL type. In order to allow communication between the application and the database we use the famous Sequelize ORM.
This is why we strongly invite you to check the Sequelize documentation before: Sequelize Documentation
First of all read the Sequelize definition of a model: Model basics
In the case of Nodea, a model is globally the database definition of an entity. This means that you will find an associated model for each entity.
Models are generated when using instructions in the generator. You will find the list of instructions and whether it includes the generation of a model in the section Instruction Definition
For instance:
add entity Product
will instantiate a model E_product that is the definition of a database table named e_product.
In your application you will find a models/
folder. This folder contains files named like [entity_name].js
(we will call them model.js
) and 2 directories named attributes/
and options/
each containing .json files.
The principle is simple, for each model.js
file found at the root of the models/
folder is associated an attributes.json
and options.json
file.
When Nodea generates a new model in application it will add these 3 files in your app/models/
folder
Note: The term options rather represents relationships, this term will be changed in next release.
The model.js file exports a class that extends from the CoreModel class declared in _core/models/model.js
. This class provides the constructor of the model and bind the association of relations between models (entities).
The CoreModel constructor also add the default core hooks defined in _core/models/hooks.js
in all models. See Hooks for more information.
Here is a simplified schema of models file architecture:
You will also find in your models/
folder an index.js
file which is very important. Indeed it is in this file that all the models present in the folder are instantiated.
1. Note in this file the inclusion of operators in models, you can use operators like this:
models.E_entity.findOne({
where: {
id: {
[models.$in]: [1,2,3]
}
}
});
2. Then there is the instantiation of Sequelize, like this:
sequelizeOptions = {
host: dbConfig.host,
logging: false,
port: dbConfig.port,
dialect: dbConfig.dialect,
dialectOptions: {
multipleStatements: true
},
define: {
timestamps: false
},
charset: 'utf8',
collate: 'utf8_general_ci',
timezone: '+00:00' // For writing to database
}
const sequelize = new Sequelize(dbConfig.database, dbConfig.user, dbConfig.password, sequelizeOptions);
3. Just after is the customAfterSync
hook which is placed just after the principle of Sequelize synchronization which occurs each time the server is started. The purpose of this hook is to analyze the toSync.json
file and execute its query content and also to store the content executed in the database in the toSyncProd.json
file to keep a trace.
More explanation on these 2 files:
This file is used by the Nodea generator when instructions are executed, it allows you to store the SQL queries that must be executed in the application so that the database also receives the modifications requested by the instruction.
This file is parsed by the app/models/index.js
file when the application server starts up. If SQL queries are present then it executes them and then empties the file.
The objective of this file is to keep all the SQL queries passed on the application during its development in the health of the generator. This file therefore makes it possible to update the exported version of the generator for this application.
This file is important during the deployment process, the remote deployed application uses this file to update its database.
The attribute files present in the /app/models/attributes
folder are .json files representing, as its name suggests, the attributes of models. The attributes are the definition of the columns of the database table.
See the Sequelize documentation for the standard definition of attributes: Column declaration
Default Nodea attributes.json files:
{
"id": {
"type": "INTEGER",
"autoIncrement": true,
"primaryKey": true
},
"version": {
"type": "INTEGER",
"defaultValue": 1
},
"createdBy": {
"type": "STRING",
"defaultValue": null,
"validate": false
},
"updatedBy": {
"type": "STRING",
"defaultValue": null,
"validate": false
}
}
In the attribute declaration you will often find the nodeaType key which is not at all a key managed by Sequelize. NodeaType represent the type of fields for the application and the generator. This value is used in particular for display in NodeaTables .
Here is the actual list of NodeaTypes: Nodea bot.js helper
The values within arrays are the different “with type” possibilities when the user is writing instructions (ex: “add field Price with type dollar” will result with currency NodeaType)
The options represent the relationships between the models. For the basics about Sequelize associations → Sequelize Association
This is what an options file looks like:
[
{
"target": "e_entity", // Target entity name
"relation": "belongsTo", // Sequelize relation type
"foreignKey": "fk_id_entity", // Database foreign key
"as": "r_alias", // Alias of the relation
"showAs": "Entity", // Display name of the relation
"structureType": "relatedTo", // Nodea structure type
}
]
You will also sometimes find the usingField key, this key represents the field used in the application to display a value.
Example:
add field My Friend related to Friends using Firstname, Lastname
will result in:
[
{
"target": "e_friends",
"relation": "belongsTo",
"foreignKey": "fk_id_friends_my_friend",
"as": "r_my_friend",
"showAs": "My Friend",
"structureType": "relatedTo",
"usingField": [{
"value": "f_firstname",
"type": "string"
}, {
"value": "f_lastname",
"type": "string"
}]
}
]
And the generated select of this relation will display Firstname - Lastname when selecting a Friend in your My Friends entity list:
There are many instructions for creating relationships between entities (and therefore between models). In the examples below we execute instructions on a Source entity to a Target entity.
Instruction | Sequelize type | Alias | Foreign key | Association table |
---|---|---|---|---|
add field my field related to myEntity | Source belongsTo Target | r_my_field | fk_id_myentity_my_field | |
add field my field related to many myEntity | Source belongsToMany Target | r_my_field | fk_id_e_source | fk_id_e_target | 11_my_field |
entity Source has one Target | Source belongsTo Target | r_target | fk_id_target | |
entity Source has one Target called My Target | Source belongsTo Target | r_my_target | fk_id_target_my_target | |
entity Source has many Target | Source hasMany Target | r_target | fk_id_source | |
entity Source has many Target called My Targets | Source hasMany Target | r_my_targets | fk_id_source_my_targets |
Hooks are a feature of Sequelize that allows you to include specific execution at a specific point in time when executing Sequelize queries.
See Sequelize's hooks documentation (Sequelize hooks) for basic behavior and features.
Here we'll see how hooks are handled in a Nodea application.
Hooks in a Nodea application are defined as follow :
const hooks = {
'hookName': {
type: 'afterCreate', // Any sequelize valid hook type
func: async (model, args) => {}
}
}
Multiple hooks of the same type can be defined as long as they have a different name.
model
parameter is the model beeing hooked, args
is the arguments provided to the query
The Nodea core already embeds some default hooks available in _core/models/hooks.js
:
beforeCreate
hook that insert the name of the user in the createdBy column before creating a database row in order to store traceabilitybeforeUpdate
hook that insert the name of the user in the updatedBy column before updating a database row in order to store traceabilityThese hooks are set by default in the _core/models/model.js
- CoreModel
class constructor and are therefore set to every model by default. You can add, delete or modify a model's hooks in the child class constructor through this.hooks
property.
To define custom hooks, you must provide your own definitions in your model's constructor. After calling the super
constructor of CoreModel
, this.hooks
is set to the defaults hooks mentioned earlier. That's where you can do anything you want with this property to make it match your needs.
As an example, let's add an afterCreate
hook that logs the newly created group id
as well as creating user's id
. We're also going to remove default hook insertCreatedBy
.
Edit app/models/e_group.js
as follow :
Validators are used to validate the format of a value according to its type before being inserted into the database.
To start with see Sequelize documentation about validator see: Sequelize validations & constraints
See directly the _core/models/validators.js
on our Github: validators.js
Work in progress.