Sequelize.js: One to Many relationship eager load with custom field - node.js

I am writting an app with node, express, sequelize and I have a little problem. I am trying to eager load a model on the "one part" of the relationship.
I get this SQL:
Executing: SELECT `albaran`.*, `cliente`.`ClienteID` AS `cliente.ClienteID`,
`cliente`.`NombreES` AS `cliente.NombreES` FROM `albaran` LEFT OUTER JOIN `cliente` AS
`cliente` ON `albaran`.`AlbaranNo` = `cliente`.`ClienteID` WHERE
`albaran`.`AlbaranNo`='2013100001';
and I need it to be:
Executing: SELECT `albaran`.*, `cliente`.`ClienteID` AS `cliente.ClienteID`,
`cliente`.`NombreES` AS `cliente.NombreES` FROM `albaran` LEFT OUTER JOIN `cliente` AS
`cliente` ON `albaran`.`ClienteID` = `cliente`.`ClienteID` WHERE
`albaran`.`AlbaranNo`='2013100001';
So I could access it at the view (now I get an empty string):
#{albaran.cliente.NombreES}
This is the code:
albaran.js
module.exports = function(sequelize, DataTypes) {
return sequelize.define("Albaran", {
AlbaranNo: { type: DataTypes.BIGINT, primaryKey: true},
ClienteID: {
type: DataTypes.STRING, //this must be string, I am not who defined the db
references: "Cliente",
referencesKey: "ClienteID"
}},{
timestamps: false,
freezeTableName: true,
tableName: 'albaran'
})
}
cliente.js
module.exports = function(sequelize, DataTypes) {
return sequelize.define("Cliente", {
ClienteID: {
type: DataTypes.STRING, primaryKey: true
},
NombreES: DataTypes.STRING
},{
timestamps: false,
freezeTableName: true,
tableName: 'cliente'
})
}
associations
db.Albaran.hasOne(db.Cliente, {as: "cliente", foreignKey: 'ClienteID'});
router
exports.albaran = function(req, res) {
db.Albaran.findAll({ include: [{ model: db.Cliente, as: "cliente" }]})
.success(function (albaranes){
res.render("albaranes", {albaranes: albaranes});
});
};
Thank you very much. I hope I explained myself clearly.
Edition:
I found the solution modifying the association:
db.Albaran.belongsTo(db.Cliente, {as: "cliente", foreignKey: 'ClienteID', primaryKey: 'ClienteID'});
db.Cliente.hasMany(db.Albaran, {as: "albaranes", foreignKey: 'ClienteID'});
Thank you everyone

This may be it:
db.Cliente.hasMany(db.Albaran, {as: "albaran", foreignKey: 'ClienteID'});
More info here: http://sequelizejs.com/documentation#associations-one-to-many
The other side is:
db.Albaran.hasOne(db.Cliente, {as: "cliente", foreignKey: 'AlbaranNo"});

Related

Sequelize double relationships

I'm doing a node.js project that allows virtual trading between user.
A User can have many trades. Each Trade will have to contain 2 fk from User, which are the id of the seller as well as the buyer.
I'm using sequelize in node.js to build up the model.
const { Sequelize, DataTypes, Model } = require('sequelize');
import {sequelize} from '../database/connection.js';
const User = sequelize.define('User', {
// Model attributes are defined here
id:{
type: DataTypes.INTEGER(11),
allowNull: false,
autoIncrement: true,
primaryKey: true
},
}, {
// Other model options go here
sequelize, // We need to pass the connection instance
freezeTableName: true
});
module.exports = User
///////////////////////////////////////////
// Trade
const { Sequelize, DataTypes, Model } = require('sequelize');
import {sequelize} from '../database/connection.js';
const Trade = sequelize.define('Trade', {
// Model attributes are defined here
id:{
type: DataTypes.INTEGER(11),
allowNull: false,
autoIncrement: true,
primaryKey: true
},
isPaid:{
type: DataTypes.BOOLEAN,
defaultValue: true
}
// fk are in relationships.js
}, {
// Other model options go here
sequelize, // We need to pass the connection instance
freezeTableName: true
});
module.exports = Trade
/////////////////////////////////////
//relationships
User.hasMany(Trade,{
foreignKey: "offererId"
})
Trade.belongsTo(User);
//
User.hasMany(Trade,{
foreignKey: "receiverId"
})
Trade.belongsTo(User);
Is this a right solution?
Using a super M:N may be your most versatile option.
User.belongsToMany(User, { through: Trade, as: "offerer", foreignKey: "offererId"})
User.belongsToMany(User, { through: Trade, as: "receiver", foreignKey: "receiverId"})
Trade.belongsTo(User)
User.hasMany(Trade)
This allows you to use the auto-generated association methods and query user on user.

Problem setting up Sequelize association - query with 'include' is failing

I'm new to Sequelize and trying to test if an n:m association I set up between two models, User and Podcast, is working. When I try to run this query, I get some kind of DB error that isn't specific about what's wrong:
User.findOne({
where: { id: id },
include: [{ model: Podcast }]
});
Does anyone know what I'm messing up? I suspect there's something wrong in how I've set up the association, like I'm referencing the names of tables slightly incorrectly, but the migration to create the association worked.
Here's my User.js model file:
module.exports = (sequelize, DataTypes) => {
const User = sequelize.define('User', {
name: {
type: DataTypes.STRING,
allowNull: false
},
email: {
type: DataTypes.STRING,
allowNull: false,
unique: true
},
photo: {
type: DataTypes.STRING
}
});
User.associate = function(models) {
// associations can be defined here
User.belongsToMany(models.Podcast, {
through: 'user_podcast'
});
};
return User;
};
And here's my Podcast.js file:
'use strict';
module.exports = (sequelize, DataTypes) => {
const Podcast = sequelize.define('Podcast', {
id: {
type: DataTypes.STRING,
primaryKey: true,
allowNull: false
},
title: {
type: DataTypes.STRING,
allowNull: false
},
thumbnail: {
type: DataTypes.STRING
},
website: {
type: DataTypes.STRING
}
});
Podcast.associate = function(models) {
// associations can be defined here
Podcast.belongsToMany(models.User, {
through: 'user_podcast'
});
};
return Podcast;
};
And here's the migration I ran to join the two tables:
'use strict';
module.exports = {
up: function(queryInterface, Sequelize) {
return queryInterface.createTable('user_podcast', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
userId: {
type: Sequelize.INTEGER,
references: {
model: 'Users',
key: 'id'
}
},
podcastId: {
type: Sequelize.STRING,
references: {
model: 'Podcasts',
key: 'id'
}
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
});
},
down: function(queryInterface, Sequelize) {
return queryInterface.dropTable('user_podcast');
}
};
And here's the project on Github for further reference:
https://github.com/olliebeannn/chatterpod
You don't need to create a migration for the M:N table. Now you have something wrong on your user_podcast model. If you are setting a M:N relation between to tables your primary key will be the combination between the foreign key from these two models. If you still want a single id primary key for your table, then you won't use belongsToMany instead use hasMany on user and podcast models pointing to a new model user_podcast.
As far as I see on your first query, it seems that you really need a M:N relation so you can define the model as you do with user and podcast like this:
module.exports = (sequelize, DataTypes) => {
const UserPodcast = sequelize.define('user_podcast', {
userId: {
// field: 'user_id', #Use 'field' attribute is you have to match a different format name on the db
type: DataTypes.INTEGER
},
podcastId: {
// field: 'podcast_id',
type: DataTypes.INTEGER
},
});
UserPodcast.associate = function(models) {
models.User.belongsToMany(models.Podcast, {
as: 'podcasts', //this is very important
through: { model: UserPodcast },
// foreignKey: 'user_id'
});
models.Podcast.belongsToMany(models.User, {
as: 'users',
through: { model: UserPodcast },
// foreignKey: 'podcast_id'
});
};
return UserPodcast;
};
I do prefer to have the belongsToMany associations on the save function where I define the join model, and you have to notice that I used as: attribute on the association. This is very important because this will help sequelize to know which association are you referring on the query.
User.findOne({
where: { id: id },
include: [{
model: Podcast,
as: 'podcasts' //here I use the previous alias
}]
});

Sequelize belongsToMany ignores additional attributes in join table

I have two models: Article and DescriptionFragment in a BelongsToMany association through a join table Descriptions, which in turn BelongsTo another model Category and also has an attribute "sequence", all defined as follows:
Article model:
'use strict';
module.exports = (sequelize, DataTypes) => {
const Article = sequelize.define('Article', {
uid: {
type: DataTypes.INTEGER,
allowNull: false,
autoIncrement: true,
primaryKey: true,
field: 'uid'
},
articleNumber: {
type: DataTypes.INTEGER(8),
allowNull: false,
field: 'article_number',
unique: true
},
}, {
underscored: true,
});
Article.associate = function (models) {
Article.belongsToMany(models.DescriptionFragment, {
through: 'Descriptions',
as: 'articleWithDescriptionFragment',
otherKey: {
name: 'descriptionFragmentId',
field: 'description_fragment_id'
},
foreignKey: {
name: 'articleId',
field: 'article_id'
},
onDelete: 'CASCADE',
onUpdate: 'CASCADE'
});
};
return Article;
};
DescriptionFragment model:
'use strict';
module.exports = (sequelize, DataTypes) => {
const DescriptionFragment = sequelize.define('DescriptionFragment', {
uid: {
type: DataTypes.INTEGER,
allowNull: false,
autoIncrement: true,
primaryKey: true,
field: 'uid'
}
}, {
charset: 'utf8',
dialectOptions: {
collate: 'utf8_general_ci'
},
timestamps: true,
paranoid: true,
underscored: true,
});
DescriptionFragment.associate = function (models) {
DescriptionFragment.belongsToMany(models.Article, {
through: 'Descriptions',
as: 'descriptionFragmentForArticle',
foreignKey: {
name: 'descriptionFragmentId',
field: 'description_fragment_id'
},
otherKey: {
name: 'articleId',
field: 'article_id'
},
onDelete: 'CASCADE',
onUpdate: 'CASCADE'
});
};
return DescriptionFragment;
};
Description model:
'use strict';
module.exports = (sequelize, DataTypes) => {
const Description = sequelize.define('Description', {
sequence: {
type: DataTypes.INTEGER,
allowNull: true
}
}, {
charset: 'utf8',
dialectOptions: {
collate: 'utf8_general_ci'
},
timestamps: true,
paranoid: true,
underscored: true,
});
Description.associate = function (models) {
Description.belongsTo(models.Category, {
as: 'categoryDescription',
foreignKey: {
name: 'categoryId',
field: 'category_id'
},
targetKey: 'uid',
onDelete: 'CASCADE',
onUpdate: 'CASCADE'
});
};
return Description;
};
Category model:
'use strict';
module.exports = (sequelize, DataTypes) => {
const Category = sequelize.define('Category', {
uid: {
type: DataTypes.INTEGER,
allowNull: false,
autoIncrement: true,
primaryKey: true,
field: 'uid'
}
}, {
charset: 'utf8',
dialectOptions: {
collate: 'utf8_general_ci'
},
timestamps: true,
paranoid: true,
underscored: true,
});
Category.associate = function (models) {
Category.hasMany(models.Description, {
foreignKey: {
name: 'categoryId',
field: 'category_id'
},
sourceKey: 'uid',
onDelete: 'CASCADE',
onUpdate: 'CASCADE'
});
};
return Category;
};
My problem comes when I try to associate a DescriptionFragment to an Article. I'm using the following code:
let addedArticle = await Article.create(newArticle);
addedArticle.addArticleWithDescriptionFragment(descFrag, {
through: {
categoryId: catId,
sequence: index
}
});
where descFrag is an instance of the DescriptionFragment model, and catId and index are integers.
When that code is run, sequelize creates the Article instance addedArticle, but then when trying to associate it to DescriptionFragment it just ignores what is in the through option. The SQL generated is, for example:
Executing (default): INSERT INTO "devdb"."Descriptions" ("created_at","updated_at","article_id","description_fragment_id") VALUES ('2019-02-07 15:11:15.376 +00:00','2019-02-07 15:11:15.376 +00:00',95,5);
As far as I could find in the documentation, the syntax I'm using is correct, and the association is created in the Descriptions table, just with null for sequence and category_id.
I'm using sequelize v4.38.1 and the database is Postgres.
I can't spot where is the error and all similar issues I have found so far were just using in sequelize v4 the old syntax for v3.
Any insight would be appreciated, thanks!
UPDATE
For now I'm using the following workaround:
await addedArticle.addArticleWithDescriptionFragment(descFrag);
let newDesc = await models.Description.find({
where: { articleId: addedArticle.uid, descriptionFragmentId: descFrag.uid }
});
await newDesc.update({ categoryId: catId, sequence: index });
which correctly sets the desired columns:
Executing (default): UPDATE "techred_dev"."Descriptions" SET "category_id"=2,"sequence"=0,"updated_at"='2019-02-08 09:21:20.216 +00:00' WHERE "article_id" = 104 AND "description_fragment_id" = 6
Of course for this to work I had to update my Description model explicitly adding the articleId and descriptionFragmentId columns:
articleId: {
type: DataTypes.INTEGER,
onDelete: 'CASCADE',
onUpdate: 'CASCADE',
references: {
model: 'Articles',
key: 'uid'
},
primaryKey: true,
field: 'article_id'
},
descriptionFragmentId: {
type: DataTypes.INTEGER,
onDelete: 'CASCADE',
onUpdate: 'CASCADE',
references: {
model: 'DescriptionFragments',
key: 'uid'
},
primaryKey: true,
field: 'description_fragment_id'
},
Still, the 'through' option in the 'add' method does not work and I have no clue why.

Sequelize nested associations with 3 tables

I have a little problem with associations on query, I've nested assocations (4 tables, Users, A, B & C, Users hasOne A, A hasMany B and belongsTo Users, B belongsTo A and hasMany C and C belongsTo B).
When I do a findOne on Users, including my 3 other tables like that :
Models.User.findOne({
where: { id: req.user.id },
include:[{
model: Models.Candidate, // Candidate Associations (user.candidate) (Table A)
as: 'candidate',
include:[{
model: Models.Experience, // (Table B) Experiences Associations (user.candidate.experiences)
as: 'experiences',
include: [{
model: Models.Service,
as: 'service'
}
}] // Service Associations (Table C)
}]
}]
})
I correctly get the searched row but the problem is on the left outer join query of the third assocations (table C), the generated query is
LEFT OUTER JOIN `Services` AS `candidate->experiences->service` ON `candidate->experiences`.`id` = `candidate->experiences->service`.`id`
But I want to have this query :
LEFT OUTER JOIN `Services` AS `candidate->experiences->service` ON `candidate->experiences`.`service_id` = `candidate->experiences->service`.`id`
So how can I do that ?
I searched some help on google and other forum and websites but I didn't manage to find my answer since few weeks.
Thanks a lot for help that you'll provide me !
Edit :
Here is my models :
User :
module.exports = (sequelize, DataTypes) => {
const User = sequelize.define('User', {
email: {
type: DataTypes.STRING,
allowNull: false,
unique: true
},
password: {
type: DataTypes.STRING,
allowNull: false
},
role: {
type: DataTypes.STRING
},
type: {
type: DataTypes.ENUM,
values: ['admin', 'candidate', 'es', 'demo'],
allowNull: false
}
});
User.associate = function (models) {
User.hasOne(models.Candidate, {
foreignKey: 'user_id',
as: 'candidate'
});
};
return User;
};
Candidate (A) :
'use strict';
module.exports = (sequelize, DataTypes) => {
const Candidate = sequelize.define('Candidate', {
user_id: {
type: DataTypes.INTEGER,
primaryKey: true
},
es_id: DataTypes.INTEGER,
description: DataTypes.TEXT,
photo: DataTypes.STRING,
video: DataTypes.STRING,
status: DataTypes.STRING,
note: DataTypes.STRING,
views: DataTypes.INTEGER
}, {});
Candidate.associate = (models) => {
Candidate.belongsTo(models.User, {
foreignKey: 'id',
onDelete: 'CASCADE'
});
Candidate.hasMany(models.Experience, {
foreignKey: 'candidate_id',
as: 'experiences'
});
};
return Candidate;
};
Experience (B) :
'use strict';
module.exports = (sequelize, DataTypes) => {
const Experience = sequelize.define('Experience', {
name: {
type: DataTypes.STRING,
allowNull: false
},
candidate_id: DataTypes.INTEGER,
poste_id: DataTypes.INTEGER,
service_id: DataTypes.INTEGER,
internship: DataTypes.BOOLEAN,
start: DataTypes.DATE,
end: DataTypes.DATE,
current: DataTypes.BOOLEAN
}, {});
Experience.associate = function (models) {
Experience.belongsTo(models.Candidate, {
foreignKey: 'id',
onDelete: 'CASCADE'
});
Experience.hasOne(models.Service, {
foreignKey: 'id',
targetKey: 'service_id',
as: 'service'
});
};
return Experience;
};
Service (C) :
'use strict';
module.exports = (sequelize, DataTypes) => {
const Service = sequelize.define('Service', {
name: {
type: DataTypes.STRING,
allowNull: false
}
}, {});
Service.associate = function (models) {
Service.belongsTo(models.Experience, {
foreignKey: 'id',
onDelete: 'CASCADE'
});
};
return Service;
};

How to force 1:n association with Sequelizejs

I'm implementing some tests to make sure my sequelize objects are saved correctly.
I have a very simple schema: Article <--> Users
An Article is published by ONE User
A User can publish MANY Articles
Here is my Article model definition:
module.exports = function(sequelize){
"use strict";
var Sequelize = require('sequelize');
...
var Article = sequelize.define("Article", {
slug: {
type: Sequelize.STRING,
unique: true,
comment: "Unique URL slug to access the article"
},
title: {
type: Sequelize.STRING,
unique: true,
allowNull: false,
validate: {
notEmpty: true
}
},
summary: {
type: Sequelize.TEXT,
allowNull: true
},
body: {
type: Sequelize.TEXT,
allowNull: true
},
published: {type: Sequelize.BOOLEAN, allowNull: false, defaultValue: true},
allowComment: {type: Sequelize.BOOLEAN, allowNull: false, defaultValue: true}
}, {
freezeTableName: true,
classMethods: {
associate: function (models)
{
Article.belongsTo(models.User, {as: "Author", foreignKey: 'author_id'});
Article.hasMany(models.Comment, {as: "Comments", foreignKey: 'article_id'});
},
articlesForIndex: function()
{
return this.findAll({
where: {published: true},
order: 'createdAt DESC',
limit: 10
});
}
},
setterMethods : {
title : function(v) {
this.setDataValue('title', v.toString());
this.setDataValue('slug', slugify(v));
}
}
});
return Article;
};
What I want to do is forcing the Article to have an Author (User). With the current definition I can create Article without Author.
Here is my test that is failing:
module.exports = function (sequelize, models) {
'use strict';
var Q = require('q');
var should = require('chai').should();
describe('Article Model', function () {
describe('Validation', function () {
...
it('should not be valid without an author', function (done) {
models.Article.create({title: 'title5', summary: 'summary', body: 'body'})
.should.be.rejected.notify(done);
});
});
});
};
On fairly recent (2.0-rc2 i believe) versions of sequelize you can change the foreign key to an object:
Article.belongsTo(User, {
as: "Author",
onDelete: 'CASCADE',
foreignKey: { name:'author_id', allowNull: false }
});
You also need to add onDelete: 'CASCADE' because we can no longer set null on delete

Resources