Using Sequelize with associations and scopes with includes in multiple files - node.js

I'm using this way to keep my Sequelize models in separate files and everything works pretty well but now I came up with the idea to have scopes with includes in it.
Something like this doesn't work:
var User = sequelize.define("User", {...}, {
scopes: {
complete: {
include: [{
model: Task
}]
}
}
});
... Since Task is (of course) not defined. Even using require('.').Task instead doesn't help at this point because User gets loaded before Task and by the time User is loaded, Task is not yet defined.
So, is there a simple and easy way without a dozen workarounds to have
associations
scopes with includes
... All of this in a separate file per model?

and if the model (Task) is not yet loaded? (in this case T comes before U and the model is loaded).
Object.keys(db).forEach(function(modelName) {
if ("associate" in db[modelName]) {
db[modelName].associate(db);
}
});
becomes
Object.keys(db).forEach(function(modelName) {
if ("associate" in db[modelName]) {
db[modelName].associate(db);
}
});
Object.keys(db).forEach(function(modelName) {
if ("loadScopes" in db[modelName]) {
db[modelName].loadScopes(db);
}
});
and the model
module.exports = function(sequelize, DataTypes) {
var User = sequelize.define("User", {
username: DataTypes.STRING
}, {
classMethods: {
associate: function(models) {
User.hasMany(models.Task)
}
}
});
return User;
};
becomes
module.exports = function(sequelize, DataTypes) {
var User = sequelize.define("User", {
username: DataTypes.STRING
}, {
classMethods: {
associate: function(models) {
User.hasMany(models.Task)
}
loadScopes: function(models) {
User.addScope('complete', {
include: [{
model: models.Task
}]
})
}
}
});
return User;
};

Using the addScope() method is better for include models in scope. This is because Sequelize models are defined alphabetically, Model A comes first before Model B. When you include Model B in Model A scope, by the time Model A runs Model B is undefined. addScope() method solves this issue.
module.exports = function(sequelize, DataTypes) {
const User = sequelize.define('User', {
username: DataTypes.STRING
}, { });
User.associate = function(models) {
// associations can be defined here
User.hasMany(models.Task);
// scopes should be defined here
User.addScope('defaultScope', {
include: [{ model: models.Task }],
});
// scopes with parameter
User.addScope('byProfile', profileId => ({
where: { profileId },
}));
};
return User;
};
using default scope:
User.findAll();
This will use the default scope by default.
using byProfile scope:
User.scope({ method: ['byProfile', profile.id] }).findAll();

Try to use Sequelize.models.Task
module.export = function(Sequelize, DataTypes){
var User = sequelize.define("User", {...}, {
scopes: {
complete: {
include: [{
model: Sequelize.models.Task
}]
}
}
});
}
Or use addScope() to add them without having to worry about order of loading each model

Related

Node JS Sequelize many-to-many relationship throwing error column id does not exist

I am building a Node JS web application. I am using Sequelize, https://sequelize.org/ for manipulating the database logic. Now, I am having a problem with bulk insert and many-to-many relationships.
I have a model called, Region with the following code.
'use strict';
const {
Model
} = require('sequelize');
module.exports = (sequelize, DataTypes) => {
class Region extends Model {
static associate(models) {
// define association here
Region.belongsToMany(models.ExchangeRequest, {
through: 'RegionExchangeRequests',
as: 'exchangeRequests',
foreignKey: "region_id",
otherKey: "exchange_request_id"
})
}
};
Region.init({
name: DataTypes.STRING,
latitude: DataTypes.FLOAT,
longitude: DataTypes.FLOAT
}, {
sequelize,
modelName: 'Region',
});
return Region;
};
Then, I have a model called, ExchangeRequest with the following code.
'use strict';
const {
Model
} = require('sequelize');
module.exports = (sequelize, DataTypes) => {
class ExchangeRequest extends Model {
static associate(models) {
// define association here
ExchangeRequest.belongsToMany(models.Region, {
through: 'RegionExchangeRequests',
as: 'regions',
foreignKey: 'exchange_request_id',
otherKey: "region_id"
})
ExchangeRequest.belongsTo(models.User, { foreignKey: 'userId', as: 'user', onDelete: 'cascade' });
}
};
ExchangeRequest.init({
exchange_rate: DataTypes.DECIMAL,
currency: DataTypes.STRING,
amount: DataTypes.DECIMAL,
buy_or_sell: DataTypes.INTEGER,
note: DataTypes.STRING,
email: DataTypes.STRING,
phone: DataTypes.STRING,
address: DataTypes.STRING,
userId: DataTypes.INTEGER
}, {
sequelize,
modelName: 'ExchangeRequest',
});
return ExchangeRequest;
};
Then I have the RegionExchangeRequest with the following code.
'use strict';
const {
Model
} = require('sequelize');
module.exports = (sequelize, DataTypes) => {
class RegionExchangeRequest extends Model {
static associate(models) {
// define association here
}
};
RegionExchangeRequest.init({
region_id: DataTypes.INTEGER,
exchange_request_id: DataTypes.INTEGER
}, {
sequelize,
modelName: 'RegionExchangeRequest',
});
return RegionExchangeRequest;
};
I have a function that is doing the bulk insert on RegionExchangeReques as follow.
const create = async ({
exchange_rate,
currency,
amount,
buy_or_sell,
note,
email,
phone,
address,
region_ids,
userId
}) => {
try {
let exchangeRequest = await ExchangeRequest.create({
exchange_rate,
currency,
amount,
buy_or_sell,
note,
email,
phone,
address,
userId
});
if (region_ids && region_ids.length > 0) {
let pivotData = [ ];
region_ids.forEach(regionId => {
pivotData.push({
exchange_request_id: exchangeRequest.id,
region_id: regionId
})
})
let regionExchangeRequests = await RegionExchangeRequest.bulkCreate(pivotData);
}
return {
error: false,
data: exchangeRequest
}
} catch (e) {
return {
error: true,
code: 500,
message: e.message
}
}
}
When the function is called, it is throwing the following error.
"column \"id\" of relation \"RegionExchangeRequests\" does not exist"
The following line is throwing the error.
let regionExchangeRequests = await RegionExchangeRequest.bulkCreate(pivotData);
What is wrong with my code and how can I fix it?
In many-to-many relationship you specified RegionExchangeRequests as through table that connects two other tables, and in sequelize you cannot call usual model methods in through table.To do so you need to create super many-to-many relationship, for more information check out this link advanced-associations-in-sequelize

Sequelize - called with something that's not a subclass of Sequelize.Model

I'm working on app but I having this issue using Node.js and Sequelize for Postgresql :
throw new Error(this.name + '.' + Utils.lowercaseFirst(Type.toString()) + ' called with something that\'s not a subclass of Sequelize.Model');
^
Error: Expense.class BelongsTo extends Association {
constructor(source, target, options) {
super(source, target, options);
<....LOT OF CODE FROM SEQUELIZE ....>
if ((fieldsOrOptions || {}).transaction instanceof Transaction) {
options.transaction = fieldsOrOptions.transaction;
}
options.logging = (fieldsOrOptions || {}).logging;
return association.target.create(values, fieldsOrOptions).then(newAssociatedObject =>
sourceInstance[association.accessors.set](newAssociatedObject, options)
);
}
} called with something that's not a subclass of Sequelize.Model
I don't understand this error, especially the last line "called with something that's not a subclass of Sequelize.Model".
Here is the models :
User model
const models = require('./index');
const Expense = models.User;
module.exports = (sequelize, DataTypes) => {
var User = sequelize.define('User', {
firstname: DataTypes.STRING,
lastname: DataTypes.STRING,
email: DataTypes.STRING,
password: DataTypes.STRING
}, {
classMethods: {
associate: function(models) {
User.hasMany(models.Expense);
}
}
});
console.log(models);
User.hasMany(Expense, {as: 'Expenses', foreignKey: 'userId'});
return User;
};
And Expense model
const models = require('./index');
const User = models.User;
module.exports = (sequelize, DataTypes) => {
var Expense = sequelize.define('Expense', {
name: DataTypes.STRING,
amount: DataTypes.INTEGER,
date: DataTypes.INTEGER
}, {
classMethods: {
}
});
Expense.belongsTo(User, { foreignKey: 'userId' });
return Expense;
};
And the controller for creating an expense :
createExpense: function(req, res) {
const user = User.findOne({ where: { id: req.params.id } });
Expense.create({
name: req.body.name,
amount: req.body.amount,
date: req.body.date,
User: user
},{
include: [
{
model: User
}
]
}).then((created) => {
res.status(200).send({ success: true, message: 'Dépense ajoutée !' });
});
}
Does someone have already see an error that look like that ? I search for few days without any issue, if someone could help I'll really appreciate,
thank !
I have a same problem before, but I found a reason. Key point is that your models must under a same sequelize class.
You can look my project mini-shop on github.
Though being 3 years late... I think, it's caused by a bad import. The import in in "User Model" for the "Expense Model" uses this line and looks kind of fishy:
const Expense = models.User;
But from a rather general point of view, this kind of error message might be a hint to an error while creating an association in a line such as User.hasMany(Expense, {as: 'Expenses', foreignKey: 'userId'});
Here is another answer regarding a similar question. For example in my case, the error came from not using the definition name to access my model (<- depends on your setup!)

A is not associated to B

Looked on the internet about similar questions/errors, none of them helped me...
Unhandled rejection SequelizeEagerLoadingError: Task is not associated to User!
My users route
router.get('', function (req, res) {
models.User.findAll({
include: [
{
model: models.Task,
as: 'tasks'
}
]
}).then(function (users) {
res.send(users);
});
});
User model
'use strict';
module.exports = (sequelize, DataTypes) => {
const User = sequelize.define('User', {
first_name: DataTypes.STRING,
last_name: DataTypes.STRING,
email: DataTypes.STRING
}, {
underscored: true
});
User.associations = function (models) {
User.hasMany(models.Task, { as: 'tasks' });
};
return User;
};
Task model
'use strict';
module.exports = (sequelize, DataTypes) => {
const Task = sequelize.define('Task', {
name: DataTypes.STRING
}, {
underscored: true
});
Task.associations = function (models) {
Task.belongsTo(models.User);
};
return Task;
};
I associate them both, and made a bidirectional relationship..
As you are using the finder function for association with as option, Sequelize cannot find that alias, as it is not defined explicitly anywhere. Please try this one:
User.associations = function (models) {
User.hasMany(models.Task, { as: 'tasks' });
};
Hope this helps.

Sequelize belongstomany associations : create a post and associate its existing tags

I got two models associates by belongsToMany associations.
Posts.js :
'use strict';
module.exports = function(sequelize, DataTypes) {
var Posts = sequelize.define('Posts', {
title: DataTypes.STRING,
body: DataTypes.STRING
}, {
classMethods: {
associate: function(models) {
Posts.belongsToMany(models.Tags, {
through: 'PostsTags',
as:'posts_has_tags',
foreignKey:'postId'
});
}
}
});
return Posts;
};
and Tags.js :
'use strict';
module.exports = function(sequelize, DataTypes) {
var Tags = sequelize.define('Tags', {
name: DataTypes.STRING
},{
classMethods: {
associate: function(models) {
Tags.belongsToMany(models.Posts, {
through: 'PostsTags',
as:'posts_has_tags',
foreignKey:'tagId'
});
}
}
});
return Tags;
};
In my db i got 2 existing tags. (id:1 and id:2)
I got trouble when i create a Post i want to associate it to one of these tags.
By running this code :
create: function(request, reply){
let post = Object.assign({}, request.payload, request.params);
models.Posts.create({
title: post.title,
body: post.body,
posts_has_tags: [{tagId:1}]
},{
include: [{model:models.Tags, as:'posts_has_tags'}]
}).then(function(newPost){
if(!newPost){
return reply('[404] Not found').code(404);
}
reply(newPost).code(200);
})
}
it runs this query :
INSERT INTO `Posts` (`id`,`title`,`body`,`createdAt`,`updatedAt`) VALUES (DEFAULT,'TITLE 1','BODY 1','2017-02-05 18:33:21','2017-02-05 18:33:21');
Executing (default): INSERT INTO `Tags` (`id`,`createdAt`,`updatedAt`) VALUES (DEFAULT,'2017-02-05 18:33:21','2017-02-05 18:33:21');
Executing (default): INSERT INTO `PostsTags` (`createdAt`,`updatedAt`,`postId`,`tagId`) VALUES ('2017-02-05 18:33:21','2017-02-05 18:33:21',70,20);
It creates a new post but also a new Tag that i don't want.
It creates the associations in poststags table but with the bad tag id (the one just created instead of the id 1)
In the promise how can i access to the setAssociation() or addAssocation() method sequelize is supposed to create on belongsToMany associations :
I got errors or undefined if i try newPost.addTags or newPost.setTags, or models.Posts.addTags or models.Posts.setTags
If anybody can help me, he's welcome.
I stuck on this for days and i'd like to understand right how i can use sequelize the best way.
I changed the alias "as" for both models :
Posts.js :
'use strict';
module.exports = function(sequelize, DataTypes) {
var Posts = sequelize.define('Posts', {
title: DataTypes.STRING,
body: DataTypes.STRING
}, {
classMethods: {
associate: function(models) {
Posts.belongsToMany(models.Tags, {
through: 'PostsTags',
as:'postHasTags',
foreignKey:'postId'
});
}
}
});
return Posts;
};
Tags.js :
'use strict';
module.exports = function(sequelize, DataTypes) {
var Tags = sequelize.define('Tags', {
name: DataTypes.STRING
},{
classMethods: {
associate: function(models) {
Tags.belongsToMany(models.Posts, {
through: 'PostsTags',
as:'tagHasPosts',
foreignKey:'tagId'
});
}
}
});
return Tags;
};
Both alias are reversed.
When i create a new post or update one, i can access to the setter built by sequelize (model.setAssociation) in the promise :
upsert: function(request, reply){
let receivedPost = Object.assign({}, request.payload, request.params);
models.Posts.find({
where:{ id:receivedPost.id }
}).then(post => {
if(post){
post.setPostHasTags(receivedPost.tags)
reply(post).code(200)
} else {
models.Posts.create({
title: receivedPost.title,
body: receivedPost.body
}).then(newPost => {
newPost.setPostHasTags(receivedPost.tags)
reply(newPost).code(200);
});
}
});

How to extend data-model with new method?

I use Sequelize to store and save data to a database.
var sequelize = require('../database.js').sequelize,
Sequelize = require('../database.js').Sequelize;
User = sequelize.define('user', {
authID: Sequelize.STRING,
name: Sequelize.STRING,
});
User.prototype.createRider = () => {
console.log('test');
};
module.exports = User;
I'm trying to extend the model with a new method to store a specific kind of user, but User.prototype.createRider doesn't work. What can be done?
There is an "expansion of models" section in their documentation
I believe you can use the classMethods object
var Foo = sequelize.define('foo', { /* attributes */}, {
classMethods: {
createRider: function(){ return 'smth' }
}
})
You can define both instance and class methods on your Model, during the define.
Change your sequelize.define as follows:
User = sequelize.define('user', {
authID: Sequelize.STRING,
name: Sequelize.STRING,
}, classMethods: {
createRider: function() {
console.log('test');
}
});
You can then use the classMethods in the expected way:
User.createRider();
See the Sequelize documentation: Model Definition.

Resources