Associations in Sequelize migrations - node.js

My app currently uses the Sequelize sync() method to create the database, and I want to change it to use the migrations system.
One of my model has belongsTo() associations with other models, and I don't really know how to make the initial migration code for these associations.
Do I have to manually create the foreign key with SQL queries, or is there some methods available?

Case 1: Database initialization
If your purpose is to add relations during initialization of database structure it is better to just use sync method instead of manually adding them using migrations. If your models are properly designed and have relations defined, they will be created automatically during execution of sync method.
Take a look at sequelize express example. In models directory you have three files:
index.js - which includes all models
task.js - task model
user.js - user model
Look at task.js content, starting from line 7 the following code creates a relation between User and Task models:
classMethods: {
associate: function(models) {
Task.belongsTo(models.User, {
onDelete: "CASCADE",
foreignKey: {
allowNull: false
}
});
}
}
If you correctly prepare your relations in model files, sync will create the foreign keys for you. Migrations aren't necessary in this case.
I encourage you to read the whole express-example readme.md and browse repository files to see how the things work with express and sequelize.
Case 2: Database structure migration
In case you already have some data which you want to keep, you need to use migration script, because the only way for sync to restructure your database is to destroy it completely alongside with all its data.
You can read about basic migrations in the sequelize docs. Unfortunately docs do not cover creating a relation. Let's assume you want to create the following relation: User belongs to Group. To create column on the user side of relation, you may use addColumn method.
queryInterface.addColumn(
'user',
'group_id',
{
type: Sequelize.INTEGER,
allowNull: true
}
)
Unfortunately there isn't a nice function (yet) to create the foreign key constraint for you, but you can do it manually using sequelize query method. Postgresql example:
queryInterface.sequelize.query("ALTER TABLE user
ADD CONSTRAINT user_group_id_fkey FOREIGN KEY (group_id)
REFERENCES group (id) MATCH SIMPLE
ON UPDATE CASCADE ON DELETE CASCADE;");
Edit: Added database structure migration case

Adding this as an answer instead of a comment (not enough rep) for #aryeh-armon answer above. It's the table name that you need to make sure exists rather than the model name. i.e. if your model is named Job and your db table is named Jobs then the migration would look look like this instead.
jobId: {
type: Sequelize.INTEGER,
references: {
model: "Jobs",
key: "id"
}
},

you can add references to the migrations
Exmaple:
user_id: {
type: Sequelize.BIGINT,
references: {
model: "users",
key: "id"
}
},
Just make sure the model you are referencing exists.

After lots of searching, I found a couple of blog posts explaining what I wanted to do. The first one no longer exists, and here's the second one
Apparently it's not really the common way to do it, but it seems more logical to me. If you want to use only the migrations, you have to use SQL queries to create the initial migration.
But anyway, I think that ezpn is right about creating the initial database with sync, and then migrate. It seems easier than using umzug and only use migrations.

Related

How to get access to entity by his name?

I've got two projects based on single database. In the first project I've got every entities classes (with annotations like #Entity) but in second I don't have it and I can't use it. But I need to make an CRUD operations based on this entities.
So, how to use this entities based only on tables names?
I try like this:
this.entityManger.getRepository('user')
but it throw me this:
UnhandledPromiseRejectionWarning: EntityMetadataNotFoundError: No metadata for "user" was found.
my TypeORMConfig looks like this:
type: 'postgres',
url: this.dbURL(),
migrationsTransactionMode: 'each',
migrationsRun: true,
synchronize: true,
keepConnectionAlive: true,
can someone tell me how to connect to entity by only his name?
thanks for any help!
i'm not sure nest supports such a thing out of the box, but you can surly use the underlying TypeORM to do so:
import {getRepository} from "typeorm";
// later in your code
const userRepo = getRepository('users'); // assuming your table name is indeed 'users'
with the repo object you can do whatever you want as it is a TypeORM repository.
note that the type on this example is unknown so every object that comes from it will be of unknown type.
you can simply supply it with a type of your choice (or interface or simply any) to avoid further typing issues.

What is the right approach of defining sequelize associations without using sequelize.sync?

The project:
Language: NodeJS,
Database: Postgres,
ORM: Sequelize v6
I've opted out of using sequelize.sync.
Instead, I am using migrations to create/update database schema.
I define associations in model definition files.
The part that confuses me, is the references option in model creation migrations.
The documentation is unclear about this option (and for the general use-case without sequelize.sync as well).
The main questions are:
Is the references option required in my specific use case?
is the references option recommended in my specific use case?
In which case would I possibly need this option?
What is the proper way to manage associations in my specific use case?
Thanks in advance
After some research (observing database changes using different approaches), trial and error I figured out that it's not possible to add configurable constraints (for example specifying onDelete action) with only references option.
So it seems the right way to implement associations is as follows:
(Note: without using sequelize.sync)
Create table1 using a migration
Create table2 using a migration - without specifying references option
Inside the migration (creation) file of the table2 add the following code (with necessary changes) to add a foreign key constraint
await queryInterface.addConstraint('tableName', {
fields: ['some-table2-field'],
type: 'foreign key',
references: {
table: 'table1',
field: 'table1Field',
},
onDelete: 'cascade',
onUpdate: 'no action',
});
Define association in table1 and table2 models as it's usually done, eg.
// table1.model.js
table1.hasMany(models.table2, {
foreignKey: 'name-of-the-key',
onDelete: 'cascade',
onUpdate: 'no action',
});
// table2.model.js
table2.belongsTo(models.table1, {
foreignKey: 'name-of-the-key',
onDelete: 'cascade',
onUpdate: 'no action',
});
So to answer my questions 1-3: you practically don't need to use references when using migrations over sequelize.sync, and 4: was described above.
If someone else has a better or more correct approach, answers and comments are welcome.

Why is loopback throwing the error: The `ModelDefinition` instance is not valid

I am taking Strongloop for a spin. I am just trying the "Getting Started" tutorials, and the basic functionality one would typically want/need.
I am using Windows and PostgresSQL, so I created a new datasource and edited the model-config.json to change the built-in models datasource to this new one, lets call it lbdev.
After that I followed the docs section about creating the tables for the built-in models. The tables were created (everything looks fine in PgAdmin). I ran the explorer and the only public API (Users) is there, so far so good.
Next, using Arc I am trying to discover the models from the lbdev schema (with empty tables) but I get the following error for each table that is there:
Oops! Something is wrong
The ModelDefinition instance is not valid.
Details: name is not unique (value: "User").
Name: ValidationError
Message: The ModelDefinition instance is not valid. Details: name is not unique (value: "User").
Details: {"context":"ModelDefinition","codes":{"name":["uniqueness"]},"messages":{"name":["is not unique"]}}
Request: /workspace/api/DataSourceDefinitions/server.lbdev/createModel
status: 422
It is like it has already been done, but the Models tree in Arc is empty. Can someone shed some light over what is going on here?
Note: There is another post with a similar problem but very little info is provided so I created a new one.
Copying my comments into an answer...
I'm not sure why you are trying to discover models on that schema... are there other tables that already existed? If so, then you want to only pull those in, and not the tables that were auto-created from the built-in LoopBack models. If you try to "discover" the models that you just generated the tables from, then you will naturally have duplicate models (they are built-in, they already exist).
If you want to manage, extend, alter, whatever the built-in models then you need to create a new model and use whatever built-in model as the base:
// common/models/visitor.json
{
"name": "Visitor",
"base": "User",
// ... other options
"properties": {
// ... additional properties to those already on User
},
"acls": [
// ... additional ACLs to those on User... careful, these might overwrite built-in restrictions!
],
// ... other overwrites/additions
}

Using sequelize.js to interface against existing database?

I'm currently working in a project where our Node.js server will perform a lot of interactions against an existing MySQL database. Thus I'm wondering if Sequelize is a good library to interface the database. From what I've read about it, it is most often used as a master of the database. But in my case it will only have select,insert,delete access and not access to modify and create tables and so on. Does Sequelize support this method of interaction with a database?
If Sequelize does indeed work good for this, what settings do i need to disable to not run into much trouble? After reading their documentation i could not find any global settings to turn it into a simple interface tool. Timestamps and such could be disabled on table definition but not globally what I saw. Any input is greatly appreciated.
There are a lot of questions in this post, I'll try to answer them all:
Disable timestamps globally:
new Sequelize(... ,{
define: {
timestamps: false
}
});
You can pass any define options to the sequelize constructor and they will be applied to all calls to sequelize.define
Mapping to an existing database
I'll try to describe some common cases here:
I want my model to have a different name to my database table:
sequelize.define('name of model', attributes, {
tableName: 'name of table'
});
My database columns are called something different than the attributes in my model:
sequelize.define('name of model', {
name_of_attribute_in_model: {
type: ...
field: 'name of field in table'
}
});
My primary key is not called id:
sequelize.define('name of model', {
a_field_totally_not_called_id: {
primaryKey: true // also allows for composite primary keys, even though the support for composite keys accross associations is spotty
autoIncrement: true
}
});
My foreign keys are called something different
X.belongsTo(Y, { foreignKey: 'something_bla' });
disclaimer: I am a sequelize maintainer :). Overall I think we have pretty good support for working with legacy DBs. Feel free to ask more questions here or on irc://irc.freenode.net#sequelizejs

Database schema with sails.js and sails-postgresql

Is there any way to set database schema with sails-postgresql waterline adapter?
By default postgres adapter allways choose default public schema in database but I want to connect it to another schema.
For example I have database dev, schema test in database dev and table users in schema test.
Now I want to select all data from table users, in sql syntax I can simply write:
SELECT * FROM test.users
How to make it work in sails ?
When I write a model that uses postgres adapter, method Users.find() will look for the table users in default public schema. I want to change it to look in schema test without interactions with my postgres database.
Is it possible?
There is support for this, although it is as-yet undocumented. You can set the schema name for a model using the meta.schemaName property, eg:
module.exports = {
tableName: 'users',
meta: {
schemaName: 'test'
},
attributes: {
...
}
};
Update
It turns out this functionality was essentially broken for several versions, but it has been revamped and released in Sails-Postgresql v0.11.0. The syntax is the same as above. The main caveat is that it will not work with multiple Waterline models sharing the same tableName.
It appears that this is a bit buggy on the latest 1.0.3 version, but I found a way to accomplish it, by doing :
postgresql: {
adapter: require('sails-postgresql'),
url: 'postgresql://postgres:blablabla#localhost:5432/simplerp',
schemaName: 'radius',
}
On your config/datastores.js file.
Peace, out!

Resources