how to create parent/child associations in sequelize? - node.js

I have a Employee and their dependents model defined in sequlieze. I have an employeeId foreign key column defined in the dependent table ( see my model class below) . but when i try to execute following command
models.Employee.findOne({where: { id: Number(id) }, include: [{ model: models.Dependent }]});
i get an error -> EagerLoadingError [SequelizeEagerLoadingError]: Dependent is not associated to Employee!
isn't specifying a foreign key in the dependent model , enough?
Employee model
module.exports = function(sequelize, DataTypes) {
const Employee = sequelize.define('Employee ', {
id : {
type: DataTypes.INTEGER(11),
allowNull: false,
autoIncrement:true,
primaryKey:true
},
Name : { ... }
Dependent model
module.exports = function(sequelize, DataTypes) {
const Dependent = sequelize.define('Dependent', {
id : {
type: DataTypes.INTEGER(11),
allowNull: false,
autoIncrement:true,
primaryKey:true
},
EmployeeId : {
type: DataTypes.INTEGER(11),
allowNull: true,
references : {
model : 'Employee ',
key:'id'
}
},
Name : { ... }

isn't specifying a foreign key in the dependent model, enough?
No, it's NOT enough if you want to use Eager Loading of sequelize.
You need to create sequelize associations for these models(Employee and Dependent for your case) in pairs, see Why associations are defined in pairs?
The references option in the model only creates an FK relationship between Dependent and Employee tables in the database(relationship for db level). Does not include associations of sequelize model(relationship for application level). When to use? see Enforcing a foreign key reference without constraints
Employee has many dependents and dependent belongs to one Employee. So you need to create One-To-Many relationships.
Employee.hasMany(Dependent);
Dependent.belongsTo(Employee);
create associations like above will create FK reference for tables implicitly.
Execution results:
Executing (default): CREATE TABLE IF NOT EXISTS "employees" ("id" SERIAL , "name" VARCHAR(255), PRIMARY KEY ("id"));
Executing (default): CREATE TABLE IF NOT EXISTS "dependents" ("id" SERIAL , "name" VARCHAR(255), "EmployeeId" INTEGER REFERENCES "employees" ("id") ON DELETE SET NULL ON UPDATE CASCADE, PRIMARY KEY ("id"));

Related

Customizing sequelize-cli generated IDs

Created a model using:
sequelize-cli model:create --name User --attributes "dispName:string,email:string,phoneNum1:string,vendorId:integer"
Which resulted in the following migration:
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('Users', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
dispName: {
type: Sequelize.STRING
},
// plus others...
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('Users');
}
};
I want to change the automatically defined ID to:
cognitoId: {
allowNull: false,
primaryKey: true,
type: Sequelize.STRING(100)
}
So:
Will sequelize be able to recognize this as the ID?
Where all do I need to make this change? I could only identify the migration file.
The model file doesn't have a definition for the cognitoId (or the original auto-generated id field): how will I be able to get the value of a User instance's cognitoId (in the data returned by queries)?
Will changing the auto-generated id field have repercussions down the line?
Is the field name id "magical"? I.e., does the primary key have to be named id?
Is there a better way to do this?
Will changing the types of the fields from Sequelize.STRING to Sequelize.STRING(100) create any issues down the line?
Why doesn't the models file generated by sequelize-cli have the id field defined?
When generating models+migrations from the command-line I couldn't find any syntax to specify the ID or any other customization for the fields.
Using:
[Node: 12.14.1, CLI: 5.5.1, ORM: 5.21.3]
PS: relatively new to NodeJS & completely new to Sequelize.
Yes
You should declare custom named PK in your model
see p.2. If you don't declare PK in your model then sequelize assumes you have id PK with an integer type, autoincremented. If you wish to assign your PK another name you should declare it in the model.
Depends on what changes you make
It is the default PK name in sequelize (see p.3). You can set different name to your PK manually declaring it in your model (see p.3)
Personally I prefer to declare all PKs in my models even if they have id name and default PK type and value.
No issues if all PK values do not exceed this length
see p.3
You can define names and types only for fields while generating models from the command line.

Why does Sequelize add extra columns to SELECT query?

When i want to get some records with joined data from the referenced tables, Sequelize adds the reference columns twice: the normal one and a copy of them, written just a little bit different.
This is my model:
module.exports = function(sequelize, DataTypes) {
return sequelize.define('result', {
id: {
type: DataTypes.INTEGER(10),
allowNull: false,
primaryKey: true,
autoIncrement: true
},
test_id: {
type: DataTypes.INTEGER(10),
allowNull: false,
references: {
model: 'test',
key: 'id'
}
},
item_id: {
type: DataTypes.INTEGER(10),
allowNull: false,
references: {
model: 'item',
key: 'id'
}
},
}, // and many other fields
{
tableName: 'result',
timestamps: false, // disable the automatic adding of createdAt and updatedAt columns
underscored:true
});
}
In my repository I have a method, which gets the result with joined data. And I defined the following associations:
const Result = connection.import('../../models/storage/result');
const Item = connection.import('../../models/storage/item');
const Test = connection.import('../../models/storage/test');
Result.belongsTo(Test, {foreignKey: 'test_id'});
Test.hasOne(Result);
Result.belongsTo(Item, {foreignKey: 'item_id'});
Item.hasOne(Result);
// Defining includes for JOIN querys
var include = [{
model: Item,
attributes: ['id', 'header_en']
}, {
model: Test,
attributes: ['label']
}];
var getResult = function(id) {
return new Promise((resolve, reject) => { // pass result
Result.findOne({
where: { id : id },
include: include,
// attributes: ['id',
// 'test_id',
// 'item_id',
// 'result',
// 'validation'
// ]
}).then(result => {
resolve(result);
});
});
}
The function produces the following query:
SELECT `result`.`id`, `result`.`test_id`, `result`.`item_id`, `result`.`result`, `result`.`validation`, `result`.`testId`, `result`.`itemId`, `item`.`id` AS `item.id`, `item`.`title` AS `item.title`, `test`.`id` AS `test.id`, `test`.`label` AS `test.label` FROM `result` AS `result` LEFT OUTER JOIN `item` AS `item` ON `result`.`item_id` = `item`.`id` LEFT OUTER JOIN `test` AS `test` ON `result`.`test_id` = `test`.`id` WHERE `result`.`id` = '1';
Notice the extra itemId, testId it wants to select from the result table. I don't know where this happens. This produces:
Unhandled rejection SequelizeDatabaseError: Unknown column 'result.testId' in 'field list'
It only works when i specify which attributes to select.
EDIT: my tables in the database already have references to other tables with item_id and test_id. Is it then unnecessary to add the associations again in the application code like I do?
A result always has one item and test it belongs to.
How can i solve this?
Thanks in advance,
Mike
SOLUTION:
Result.belongsTo(Test, {foreignKey: 'test_id'});
// Test.hasMany(Result);
Result.belongsTo(Item, {foreignKey: 'item_id'});
// Item.hasOne(Result);
Commenting out the hasOne, hasMany lines did solve the problem. I think I messed it up by defining the association twice. :|
Sequelize uses these column name by adding an id to the model name by default. If you want to stop it, there is an option that you need to specify.
underscored: true
You can specify this property on application level and on model level.
Also, you can turn off the timestamps as well. You need to use the timestamp option.
timestamps: false
Although your solution fixes your immediate problem, it is ultimately not what you should be doing, as the cause of your problem is misunderstood there. For example, you MUST make that sort of association if making a Super Many-to-Many relationship (which was my problem that I was trying to solve when I found this thread). Fortunately, the Sequelize documentation addresses this under Aliases and custom key names.
Sequelize automatically aliases the foreign key unless you tell it specifically what to use, so test_id becomes testId, and item_id becomes itemId by default. Since those fields are not defined in your Result table, Sequelize assumes they exist when generating the insert set, and fails when the receiving table turns out not to have them! So your issue is less associating tables twice than it is that one association is assuming extra, non-existing fields.
I suspect a more complete solution for your issue would be the following:
Solution
Result.belongsTo(Test, {foreignKey: 'test_id'});
Test.hasMany(Result, {foreignKey: 'test_id'});
Result.belongsTo(Item, {foreignKey: 'item_id'});
Item.hasOne(Result, {foreignKey: 'item_id'});
A similar solution fixed my nearly identical problem with some M:N tables.

Node/Express Project: Create and Sync a "Join Class/Model" to a Postgres Database

I'm building a Node/Express/Postgres version of an app that I already built in Rails. I'm learning Node, so I figured I'd rebuild something that I know works.
For now, I'm dumping everything in one file (set up my database, defined my models, etc.), just to make sure I have everything set up correctly before I divvy them up into different files.
I set up my postgres database at the very top of the file, like so:
var Sequelize = require('sequelize');
var db = new Sequelize('my_database_name', 'my_username', null, {
host: 'localhost',
dialect: 'postgres',
});
With regard to my models, I have a Politician model:
var Politician = db.define("politician", {
name: {
type: Sequelize.STRING,
},
politicalParty: {
type: Sequelize.STRING
}
});
A Category model:
var Category = db.define("category", {
name: {
type: Sequelize.STRING
},
keywords: {
type: Sequelize.ARRAY(Sequelize.TEXT)
},
});
And a join model of Politician and Category, called "Interest". Because Interest is a join model, it will have a "politicianId" and "categoryId" properties....but will those properties automatically generate in the database? And so, is this how I would define the Interest model, with no properties?
Interest Model:
var Interest = db.define("interest")
Or, will I have to be specific, and create "politicianId" and "categoryId" properties? Like so:
Interest Model:
var Interest = db.define("interest", {
id: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true
},
categoryId: {
type: Sequelize.INTEGER,
foreignKey: true
},
politicianId: {
type: Sequelize.INTEGER,
foreignKey: true
}
});
Also, do I need the "foreignKey: true" bit? Or will it automatically know that those properties are foreign keys? Also, do I need the "id" property? I know models automatically create their own primary key "id"...but again, I've been at this for hours, looking at docs, and trying everything.
I then defined my associations (again, all of this is the same file):
Politician.belongsToMany(Category, {through: "Interest"});
Category.belongsToMany(Politician, {through: "Interest"});
The Node/Sequelize docs seems to suggest that defining those 2 associations above will automatically "create a new model called Interest with the equivalent foreign keys politicianId and categoryId." So, do I even need to define a "Interest" model? Also, do I need the follow associations to describe that Interest belongs to Politician and Category?
Interest.belongsTo(Politician);
Interest.belongsTo(Category);
If I don't write the associations saying that Interest belongs to Politican and Catetory, I don't get the "politicianId" and "categoryId" columns in the Interest table. Just the "id" and createdAt/updatedAt columns.
I then created an instance of Politician, Category, and Interest, to persist everything to the database, to see if everything is there and set up correctly:
Politician Object:
var politician1 = Politician.sync({force: true}).then(function(){
return Politician.create(aPoliticianObjectDefinedInthisFile);
});
This works perfectly. I see this object in the politician table in the database.
Category Object:
var category1 = Category.sync({force: true}).then(function(){
return Category.create(aCategoryObjectDefinedInThisFile);
});
This works perfectly. I see this object in the category table in the database.
Here is what doesn't work. Creating an instance/object of Interest and synching it to the database. My thinking is, if I put integers as values, it will know that "politicianId: 1" means point to the politician object with an id of 1, and the same for "categoryId: 1". But when I write it as I have it below, the Interest table doesn't even show up in the Postgres database at all.
Interest Object:
Interest.sync({force: true}).then(function(){
return Interest.create(
{
politicianId: 1,
categoryId: 1
}
);
});
However, when I create the object of Interest like this, with no properties defined, the Interest table appears in the database, along with the "politicianId" and "categoryId" columns, however, those columns are empty. The object's primary id is in there at 1, and the "createdAt" and "updatedAt" columns have data too. But the foreign key columns are blank.
Interest Object:
Interest.sync({force: true}).then(function()
{
return Interest.create(
{
// No properties defined.
}
);
}
);
Sorry for this long post, lol, but, in all:
Am I creating the "Interest" model correctly?
Am I writing the associations for "Interest" correctly?
Do I even need to write associations for Interest, if I already have associations for its parent classes, Politican and Category defined?
In my Rails app, my associations for Politican and Category are like so:
Politician has_many interests
Politican has_many categories through interests
Category has_many interests
Category has_many politicians through interests
Interest belongs_to politician
Interest belongs_to category
But I use the "belongsToMay" association in Node because I got an error telling me to do so.
Basically, I need to create an instance of Politician, an instance of Category, and an instance of Interest that has "politicianId" and "categoryId" columns that point to those aforementioned instances of those classes.
politicanABC -- id: 1
categoryABC -- id: 1
instanceABC -- id: 1; politicanId: 1 (referring to politicanABC); categoryid: 1 (referring to categoryABC).
My app is set up like that in Rails and works wonderfully.
Help and thank you in advance :-)
You don't have to define the Interest model if you are not going to add any additional fields. Sequelize will internally define the model and add all required fields once you do following:
Politician.belongsToMany(Category, {through: "Interest"});
Category.belongsToMany(Politician, {through: "Interest"});
Sync needs to run on database level and not on tables since Interest model is implicit at this point.
db.sync({force: true});
Sequelize will add relationship build methods on both Politician and Category instances. Category will have methods addPolitician(), addPoliticians([]), setPoliticians([]), getPliticians(). Politician instances will have similar functions to associate categories to them. You can connect these after create option is performed on both objects successfully.
Politician.create({name: 'John Doe', politicalParty: 'Nice Party'})
.then(function(politician) {
Category.create({name: 'Nicers'})
.then(function(category) {
politician.addCategory(category);
});
});
You can also search and associate existing items using helper methods. Alternatively you can associate objects manually by accessing db.models.Interest model and running creates on it.

How to create assocations in Sequelize migrations?

I am using migrations to create entities. Naturally, some have relations between them. Until now, by using sync(true), I enjoyed the benefit of Sequelize implementing the relations for me at the database level.
How do I express new relations in a migration?
One-to-many: Should I be taking care of the foreign key columns?
Many-to-many: Should I be creating the intermediate table and setting foreign keys on each entity's table?
Or: Am I supposed to run the migration and then sync(false)?
What about relations that are no longer relevant?
When I use migrations, i set sync:false.
And to set associations, my model ends up like so:
User.js:
module.exports = function(sequelize, DataTypes) {
var User = sequelize.define("User", {
"fname": {
"type": DataTypes.STRING,
"unique": false,
"allowNull": false
},
"lname": {
"type": DataTypes.STRING,
"allowNull": false
}
}, {
"tableName": "Users",
"classMethods": {
"associate": function (models) {
Locale.hasMany(models.Permissions);
}
}
});
return User;
};
This will still create the table if it doesn't exist. However I recommend that creating a table is still part of a migration. Please note that in your migration to create a table to include the id, updatedAt, createdAt and PermissionId columns (for the example above)

REFERENCE KEY in node-orm2

I am working on REST API based on node.js and i chose postgresql database to store data. Suppose that this database has two tables names User and Comment. Any Comment belongs to One User and when we decide to remove an User, the Comment's of him/her must be removed. So, I designed my table as follows:
CREATE TABLE User(
user_id SERIAL,
username VARCHAR(32) NOT NULL,
password VARCHAR(32) NOT NULL,
CONSTRAINT pk_user PRIMARY KEY (user_id),
CONSTRAINT uq_user UNIQUE (username)
)
CREATE TABLE Comment(
comment_id SERIAL,
user_id INTEGER NOT NULL,
content TEXT NOT NULL,
CONSTRAINT pk_cmnt PRIMARY KEY (comment_id),
CONSTRAINT fk_cmnt FOREIGN KEY (user_id) REFERENCES User(user_id)
ON UPDATE CASCADE ON DELETE CASCADE
)
But i don't run this code and use node-orm2 instead. I designed two simple models to handle this simple code:
var User = db.define('user', {
username: {
type: 'text',
size: 32, // VARCHAR(32)
required: true, // NOT NULL
unique: true // UNIQUE INDEX
},
password: {
type: 'text',
size: 32, // VARCHAR(32)
required: true // NOT NULL
}
}, {
id: 'user_id' //SERIAL
});
var Cmnt = db.define('comment', {
content: {
type: 'text',
required: true // NOT NULL
}
}, {
id: 'comment_id' //SERIAL
});
Cmnt.hasOne('user', User, {required: true}); // CREATE ASSOCIATION KEY
and synchronize database with these models :
db.sync();
Now, I want to insert new comment belongs to user which user_id doesn't exist. So, the Comment model accepts this and insert the row into comment table.
My question is, how can i do some things like REFERENCE KEY and the ON UPDATE CASCADE ON DELETE CASCADE ?
Thanks in advance :)
Try to use deferrable like this:
CREATE TABLE Comment(
comment_id SERIAL,
user_id INTEGER NOT NULL,
content TEXT NOT NULL,
CONSTRAINT pk_cmnt PRIMARY KEY (comment_id),
CONSTRAINT fk_cmnt FOREIGN KEY (user_id) REFERENCES User(user_id)
ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED
)

Resources