In sequelize, if I have an association like :
User.hasMany(models.Article, { onDelete: 'cascade', hooks: true});
Sequelize will automatically add UserId column to Article table. But now I want to add more like UserName and Email to Article so when I query an article I have author's name and I do not need to query again by UserId.
How can I do that ? I tried
User.hasMany(models.Article, { onDelete: 'cascade', hooks: true, foreignKey: {'UserId': 'id', 'UserName': 'name' } });
but it only UserId appear in Article table.
Here is my User model:
"use strict";
var bcrypt = require('bcryptjs');
module.exports = function (sequelize, DataTypes) {
var User = sequelize.define('User', {
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true
},
name: {
type: DataTypes.STRING,
primaryKey: true
},
password: {
type: DataTypes.STRING,
allowNull: false
},
email: {
type: DataTypes.STRING,
primaryKey: true
},
role: {
type: DataTypes.STRING,
allowNull: false
},
activeToken: {
type: DataTypes.STRING,
allowNull: false,
},
status: {
type: DataTypes.BOOLEAN,
defaultValue: false,
allowNull: false
},
avatar: {
type: DataTypes.STRING,
allowNull: true
},
phone: {
type: DataTypes.STRING,
allowNull: true
}
}, {
instanceMethods: {
updatePassword: function (newPass, callback) {
var self = this;
bcrypt.genSalt(10, function (err, salt) {
bcrypt.hash(newPass, salt, function (err, hashed) {
self.update({
password: hashed
}).then(callback);
});
});
},
updateStatus: function (status, callback) {
this.update({
status: status
}).then(callback);
},
comparePassword: function (password, callback) {
bcrypt.compare(password, this.password, function (err, isMatch) {
if (err) {
throw err;
}
callback(isMatch);
});
},
updateRole: function (newRole, callback) {
this.update({
role: newRole
}).then(callback);
}
},
classMethods: {
createUser: function (newUser, callback) {
bcrypt.genSalt(10, function (err, salt) {
bcrypt.hash(newUser.password, salt, function (err, hash) {
newUser.password = hash;
User.create(newUser).then(callback);
});
});
},
getUserById: function (id, callback) {
var query = {
where: {
id: id
}
}
User.findOne(query).then(callback);
},
getUserByUsername: function (username, callback) {
var query = {
where: {
username: username
}
};
User.findOne(query).then(callback);
},
getUserByEmail: function (email, callback) {
var query = {
where: {
email: email
}
};
User.findOne(query).then(callback);
},
getAllUser: function (callback) {
User.findAll().then(callback);
},
deleteUser: function (userId, callback) {
var query = {
where: {
id: userId
}
};
User.destroy(query).then(callback);
},
associate: function (models) {
User.hasMany(models.Article, { onDelete: 'cascade', hooks: true, onUpdate: 'cascade', foreignKey: {'UserId': 'id', 'UserName': 'name' } });
User.hasMany(models.Comment, { onDelete: 'cascade', hooks: true, onUpdate: 'cascade' });
User.hasMany(models.Device, { onDelete: 'cascade', hooks: true, onUpdate: 'cascade' });
}
},
tableName: 'User'
});
return User;
};
You don't need to run 2 Queries to get user information. Just add a belongsTo relation from Article to User
User.hasMany(models.Article, { onDelete: 'cascade', hooks: true});
Article.belongsTo(models.User);
and query the Articles like this
Article.findAll({ where: <condition>, include: {
model: User,
attributes: ['name', 'email']
} });
you will have the result in the following format:
[.....
{
articleId: 23,
articleTitle: "<Some String>",
User: {
name: "User Name",
email: "User Email"
}
}
........
]
On a separate note, your User model has multiple primary keys, which may not work as expected. If you want to use a composite primary key then there is a separate approach for that.
Related
I am trying to fetch data from DB with sequelize. The many to many relationships between users and roles. When i fetch the users does not include the roles.
The code look like:
user model
// model defines the user objects
const userModel = (sequelize, Sequelize) => {
const users = sequelize.define("user", {
id: {
type: Sequelize.STRING,
allowNull: false,
primaryKey: true,
},
firstname: {
allowNull: false,
type: Sequelize.STRING,
},
lastname: {
allowNull: false,
type: Sequelize.STRING,
},
password: {
allowNull: false,
type: Sequelize.STRING,
},
email: {
allowNull: false,
type: Sequelize.STRING,
},
image: {
allowNull: true,
type: Sequelize.STRING,
},
});
//don not show password and id
users.prototype.toJSON = function () {
let values = Object.assign({}, this.get());
delete values.password;
delete values.id;
return values;
};
return users;
};
export default userModel;
Roles model
// model defines the events objects
const rolesModel = (sequelize, Sequelize) => {
const roles = sequelize.define("roles", {
id: {
type: Sequelize.STRING,
allowNull: false,
primaryKey: true,
},
name: {
allowNull: false,
type: Sequelize.STRING,
},
description: {
allowNull: true,
type: Sequelize.STRING,
},
});
return roles;
};
export default rolesModel;
The associations:
db.users.associate = (db) => {
db.users.belongsToMany(db.roles, {
through: "userroles",
constraints: false,
foreignKey: "rolesId",
});
};
db.roles.associate = (db) => {
db.roles.belongsToMany(db.users, {
through: "userroles",
constraints: false,
foreignKey: "userId",
});
};
There are two controller functions that are adding and fetching the user data
Controller
User.create(userDetails)
.then(() => {
let roles = req.body.roles;
roles.forEach(async (element) => {
let role = await Roles.findByPk(element);
if (role) {
await Userroles.create({
id: uniqid(),
rolesId: element,
userId: userId,
});
} else {
logger.warn(`tried adding to ${userId} a none existent role`);
}
});
})
// get user
let user = await User.findOne({
where: { email: username },
include: { model: db.roles },
});
So the roles are only a empty array when I try getting user details:
"firstname": "Mathew",
"lastname": "Murimi",
"email": "******#gmail.com",
"image": null,
"createdAt": "2022-02-12T22:56:40.000Z",
"updatedAt": "2022-02-12T22:56:40.000Z",
"roles": []
Receive the user created in the then, add the id of "newUser" in "userId"
User.create(userDetails)
.then((**newUser**) => {
let roles = req.body.roles;
roles.forEach(async (element) => {
let role = await Roles.findByPk(element);
if (role) {
await Userroles.create({
id: uniqid(),
rolesId: element,
userId: **newUser.id**,
});
} else {
logger.warn(`tried adding to ${**newUser.id**} a none existent role`);
}
});
})
I am struggling when using sequelize migrations and a many-to-many relationship between Users and Roles.
This is the Users model:
'use strict';
module.exports = (sequelize, DataTypes) => {
const user = sequelize.define('Users', {
username: DataTypes.STRING,
name: DataTypes.STRING,
email: DataTypes.STRING,
password: DataTypes.STRING
}, {});
user.associate = function(models) {
// associations can be defined here
user.belongsToMany(models.Roles, {
through: models.UserRoles
});
};
return user;
};
This is the Roles model:
'use strict';
module.exports = (sequelize, DataTypes) => {
const role = sequelize.define('Roles', {
name: DataTypes.STRING
}, {});
role.associate = function(models) {
// associations can be defined here
role.belongsToMany(models.Users, {
through: models.UserRoles
});
};
return role;
};
This is the "create-user" migration:
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('Users', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
username: {
type: Sequelize.STRING,
allowNull: true,
len: [0, 20]
},
name: {
type: Sequelize.STRING,
allowNull: true,
},
email: {
type: Sequelize.STRING,
allowNull: false
},
password: {
type: Sequelize.STRING,
allowNull: false
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('Users');
}
};
This is the "create-role" migration:
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('Roles', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
name: {
type: Sequelize.STRING
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('Roles');
}
};
This is the userRoles Model:
'use strict';
module.exports = (sequelize, DataTypes) => {
const user_role = sequelize.define('UserRoles', {
userId: DataTypes.INTEGER,
roleId: DataTypes.INTEGER
}, {});
user_role.associate = function(models) {
// associations can be defined here
};
return user_role;
};
And last one the "user-roles" migration:
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('UserRoles', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
userId: {
primaryKey: true,
type: Sequelize.INTEGER,
allowNull: false,
references: {
model: 'Users',
key: 'id'
}
},
roleId: {
primaryKey: true,
type: Sequelize.INTEGER,
allowNull: false,
references: {
model: 'Roles',
key: 'id'
}
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('UserRoles');
}
};
The problem happens when I try to access to the user.setRoles() from a controller:
exports.signup = (req, res) => {
console.log('creating new user', req.body.username);
// Save User to Database
User.create({
username: req.body.username,
email: req.body.email,
password: bcrypt.hashSync(req.body.password, 8)
})
.then(user => {
console.log('USER ADDED');
if (req.body.roles) {
Role.findAll({
where: {
name: {
[Op.or]: req.body.roles
}
}
}).then(roles => {
console.log('ROLES ', roles);
user.setRoles(roles).then(() => {
res.send({ message: "User was registered successfully!" });
});
});
} else {
console.log('NO ROLES > Normal User');
// user role = 1
user.setRoles([1]).then(() => {
res.send({ message: "User was registered successfully!" });
});
}
})
.catch(err => {
console.log('ERROR: ', err);
res.status(500).send({ message: err.message });
});
};
When I console.log the user using : console.log(Object.keys(user.__proto__)) I get this array where the special methods haven't been created, any idea what I am doing wrong?
Array(7) ["_customGetters", "_customSetters", "validators", "_hasCustomGetters", "_hasCustomSetters", "rawAttributes", "_isAttribute"]
Many thanks for your help!
Just call all associate functions after registering models. For instance:
const models = path.join(__dirname, 'models')
const db = {}
fs.readdirSync(models)
.filter(function (file) {
return (file.indexOf('.') !== 0) && (file.slice(-3) === '.js')
})
.forEach(function (file) {
var model = sequelize['import'](path.join(models, file))
db[model.name] = model
})
Object.keys(db).forEach(function (modelName) {
if (db[modelName].associate) {
db[modelName].associate(db)
}
})
I am trying to delete all contents from a n:m association table.
I have the tables: MenuItems and UserGroups like this, but I am using migrations to genererate the database:
MenuItems
module.exports = (sequelize, DataTypes) => {
const MenuItems = sequelize.define('MenuItems', {
title: {
type: DataTypes.STRING,
allowNull: false
}
// more fields...
}, {
freezeTableName: true
});
MenuItems.associate = function(models) {
MenuItems.belongsToMany(models.UserGroups, { through: 'MenuItemUserGroups' });
};
UserGroups
module.exports = (sequelize, DataTypes) => {
const UserGroups = sequelize.define('UserGroups', {
name: {
type: DataTypes.STRING,
unique: true
}
}, {
freezeTableName: true
});
UserGroups.associate = function(models) {
// Associations to other models
UserGroups.belongsToMany(models.MenuItems, { through: 'MenuItemUserGroups' });
};
return UserGroups;
};
The association table is generated with the following migration:
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('MenuItemUserGroups', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
menuItemId: {
type: Sequelize.INTEGER,
allowNull: false,
references: {
model: 'MenuItems',
key: 'id'
},
onUpdate: 'cascade',
onDelete: 'cascade'
},
userGroupId: {
type: Sequelize.INTEGER,
allowNull: false,
references: {
model: 'UserGroups',
key: 'id'
},
onUpdate: 'cascade',
onDelete: 'cascade'
}
//more fields
});
}
I try to delete using the following sequelize code:
models.MenuItems.findOne({ where: { id: 1 }, include: [{ all: true }] }).then(menuItem => {
if(req.body.userGroups.length <= 0) {
menuItem.setUserGroups([]).then(result => {
console.log(result);
});
The SQL that is generated is the following:
DELETE FROM `MenuItemUserGroups` WHERE `UserGroupId` = 1 AND `MenuItemId` IN (NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL)
[ 0 ]
where UserGroupId IN (NULL) will always return nothing. If that part is removed, the query works.
Could this have something to do with some naming convention?
I'm getting this error when trying to associate a like to a post.
Unhandled rejection SequelizeDatabaseError: null value in column
"userId" violates not-null constraint
Now the following code gets the post id and user id ok, i did a console log. What could i be doing wrong ?
routes/posts.js
router.post('/:userId/like/:postId', (req, res)=> {
models.Post.findOne({
where:{
id: req.params.postId
}
})
.then( (like) => {
if(like){
models.Likes.create({
where:{
userId: req.params.userId,
postId: req.params.postId
},
like:true
}).then( (result) => {
res.status(200).send({
message: 'You have like this post',
like: result
})
})
}
}).catch( (err) => {
res.status(401).send({
message: "Something went wrong",
err: err
})
})
})
here is the likes migration
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('Likes', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
like: {
type: Sequelize.BOOLEAN
},
userId: {
allowNull: false,
type: Sequelize.INTEGER,
references: {
model: 'Users',
key: 'id'
}
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('Likes');
}
};
Posts migration
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('Posts', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
title: {
type: Sequelize.STRING
},
post_content: {
type: Sequelize.STRING
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
},
userId: {
type: Sequelize.INTEGER,
references: {
model: 'Users',
key: 'id'
}
},
username: {
type: Sequelize.STRING
},
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('Posts');
}
};
Like model
'use strict';
module.exports = function(sequelize, DataTypes) {
const Like = sequelize.define('Likes', {
like:{
type:DataTypes.BOOLEAN,
allowNull:true
}
}, {});
Like.associate = function(models) {
Like.belongsTo(models.User, {
onDelete: "CASCADE",
sourceKey: 'userId'
})
Like.belongsTo(models.Post, {
onDelete: "CASCADE",
sourceKey: 'likeId'
})
}
return Like;
}
Post.model
module.exports = (sequelize, DataTypes) => {
const Post = sequelize.define('Post', {
title: DataTypes.STRING,
post_content: DataTypes.STRING,
username: DataTypes.STRING
}, {});
Post.associate = function(models) {
Post.belongsTo(models.User, { foreignKey: 'userId', targetKey: 'id' });
Post.hasMany(models.Likes, { foreignKey: 'postId', sourceKey: 'id' });
};
return Post;
};
extra
add_postId_to_likes
'use strict';
module.exports = {
up: function (queryInterface, Sequelize) {
return queryInterface.addColumn(
'Likes',
'postId',
{
type: Sequelize.INTEGER,
allowNull: true,
references: {
model: 'Posts',
key: 'id',
}
}
)
},
down: function (queryInterface, Sequelize) {
return queryInterface.removeColumn(
'Likes',
'postId'
)
}
};
In your create call in resolver you are not giving it the necessary values, you have a where clause but not actually giving it the value for required userId.. looks like the only value in your model is the Boolean you are setting
I figured it out.
I just used body instead of params for the postId.
router.post('/like', (req, res)=> {
models.Likes.create({
postId: req.body.postId,
userId: req.user.id,
like:true
}).then( (result) => {
res.status(200).send({
message: 'You have like this post',
like: result
});
}).catch( (err) => {
res.status(401).send({
message: "Something went wrong",
err: err
})
})
})
change my like model to this, i was using sourceKey instead of foreign keys
module.exports = function(sequelize, DataTypes) {
const Like = sequelize.define('Likes', {
like:{
type:DataTypes.BOOLEAN,
allowNull:true
},
// userId: {
// type: sequelize.INTEGER,
// references: {
// model: 'Users',
// key: 'id'
// }
// },
}, {});
Like.associate = function(models) {
Like.belongsTo(models.User, {
onDelete: "CASCADE",
foreignKey: 'userId'
})
Like.belongsTo(models.Post, {
onDelete: "CASCADE",
foreignKey: 'likeId'
})
}
return Like;
}
So now i can like a post, and it will attach the postId along with the usersId on the likes table.
like this
I am trying to create a user model with sequelize in nodejs, but it doesn't seem that my beforeCreate isn't actually being called. I have looked at the documentation and multiple examples on the internet but I can't get it working.
The model code is as follows:
"use strict";
const bcrypt = require('bcrypt');
require('dotenv').config()
module.exports = function(sequelize, DataTypes) {
var User = sequelize.define("User", {
username: {
type: DataTypes.STRING,
unique: true,
allowNull: false
},
password: {
type: DataTypes.STRING,
allowNull: false
},
emailAddress: {
type: DataTypes.STRING,
unique: true,
allowNull: false
},
lastLogin: {
type: DataTypes.DATE,
allowNull: true
}
});
function cryptPassword(password, callback) {
bcrypt.genSalt(process.env.SALT_ROUNDS, function(err, salt) {
if (err)
return callback(err);
bcrypt.hash(password, salt, function(err, hash) {
return callback(err, hash);
});
});
};
User.beforeCreate(function(model, options) {
cryptPassword(model.password, function(err, hash) {
model.password = hash;
});
});
return User;
};
you should define your hook inside of your model
var User = sequelize.define("User", {
username: ...
}, {
hooks: {
beforeCreate: function(){...}
}
});