I have a model. It is for the intermediate(pivot) table.
UserCars.init({
carId: DataTypes.INTEGER,
userId: DataTypes.INTEGER,
title: DataTypes.STRING,
}, {
timestamps: false,
sequelize,
modelName: 'UserCars',
});
and here is my migration for this:
'use strict';
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.createTable('UserCars', {
carId: {
allowNull: false,
type: Sequelize.INTEGER,
references: {
model: 'Cars',
key: 'id'
},
},
userId: {
allowNull: false,
type: Sequelize.INTEGER,
references: {
model: 'Users',
key: 'id'
},
},
title: {
type: Sequelize.STRING
},
}, {
uniqueKeys: {
Items_unique: {
fields: ['carId', 'userId']
}
}
});
},
down: async (queryInterface, Sequelize) => {
await queryInterface.dropTable('UserCars');
}
};
And I'm doing this below to create/update it:
userCar = await UserCars.findOne({
where: {
carId: 10,
userId: req.user.id,
}
});
if(userCar) {
userCar.userId = 20; // <--- This doesn't change
userCar.title = 'some other thing'; // <--- This changes
await userCar.save();
} else {
userCar = await UserCars.create({
userId: 20,
title: 'something'
});
}
The problem is, the title is being updated but the userId is not.
(I believe) this is due to constraints, you cannot use instance method to update FK value.
You need to use M-N association functions, or otherwise you could use raw SQL.
const car = await Car.findByPk(10);
const user = await User.findByPk(newValue);
// This also takes care of deleting the old associations
await car.setUsers(user, {
through: {'title': 'new value'}
});
I hope the upsert function is implemented in the future.
ref: https://github.com/sequelize/sequelize/issues/11836
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'm having trouble setting an attribute on a junction table.
I have a Many-to-Many association defined between two models UserModel and HangModel, through a custom table HangUsers.
const HangModel = rootRequire('/models/Hang');
const UserModel = rootRequire('/models/User');
const HangUsers = database.define('HangUsers', {
id: {
type: Sequelize.INTEGER(10).UNSIGNED,
primaryKey: true,
autoIncrement: true,
},
hangId: {
type: Sequelize.INTEGER(10).UNSIGNED,
references: {
model: HangModel,
key: 'id',
},
},
userId: {
type: Sequelize.INTEGER(10).UNSIGNED,
references: {
model: UserModel,
key: 'id',
},
},
rsvp: {
type: Sequelize.STRING,
allowNull: false,
validate: {
isIn: {
args: [ 'pending', 'joined' ],
msg: 'The rsvp provided is invalid',
},
},
},
});
UserModel.hasMany(HangUsers, { as: 'invitations' });
HangModel.hasMany(HangUsers, { as: 'invites' });
UserModel.belongsToMany(HangModel, { through: HangUsers });
HangModel.belongsToMany(UserModel, { through: HangUsers });
The through table has a column rsvp, that I'm trying to populate when I add users to a hang:
const hang = await HangModel.create();
await hang.addUser(user, { through: { rvsp: 'joined' } });
However, I'm getting an error:
AggregateError
at recursiveBulkCreate (/Users/sgarza62/ditto-app/api/node_modules/sequelize/lib/model.js:2600:17)
at processTicksAndRejections (internal/process/task_queues.js:97:5)
at async Function.bulkCreate (/Users/sgarza62/ditto-app/api/node_modules/sequelize/lib/model.js:2824:12)
at async Promise.all (index 0)
at async BelongsToMany.add (/Users/sgarza62/ditto-app/api/node_modules/sequelize/lib/associations/belongs-to-many.js:740:30)
at async /Users/sgarza62/ditto-app/api/routes/hangs.js:121:3 {
name: 'AggregateError',
errors: [
BulkRecordError [SequelizeBulkRecordError]: notNull Violation: HangUsers.rsvp cannot be null
at /Users/sgarza62/ditto-app/api/node_modules/sequelize/lib/model.js:2594:25
at processTicksAndRejections (internal/process/task_queues.js:97:5) {
name: 'SequelizeBulkRecordError',
errors: [ValidationError],
record: [HangUsers]
}
]
}
When I allow null on the rsvp column, the HangUsers row is created, but the rsvp value is NULL.
It seems the { through: { rsvp: 'joined' } } parameter is being ignored.
I've done this all according to the BelongsToMany docs and the Advanced M:N Associations docs, where it says:
However, defining the model by ourselves has several advantages. We can, for example, define more columns on our through table:
const User_Profile = sequelize.define('User_Profile', {
selfGranted: DataTypes.BOOLEAN
}, { timestamps: false });
User.belongsToMany(Profile, { through: User_Profile });
Profile.belongsToMany(User, { through: User_Profile });
With this, we can now track an extra information at the through table,
namely the selfGranted boolean. For example, when calling the
user.addProfile() we can pass values for the extra columns using the
through option.
Example:
const amidala = await User.create({ username: 'p4dm3', points: 1000 });
const queen = await Profile.create({ name: 'Queen' });
await amidala.addProfile(queen, { through: { selfGranted: false } });
const result = await User.findOne({
where: { username: 'p4dm3' },
include: Profile
});
console.log(result);
Just a typo on the attribute name: 'rvsp' should be 'rsvp'.
I run node 10.16.3 and sequelize 5.19.5. I created two models with a many to many relationship between them, using sequelize model:generate. It created both model and migration files. Supposedly, when I specify a many to many association, sequelize should generate the join table by itself, and also generate add methods on those models, so I can associate them properly. None of those things happened in my case after I ran sequelize db:migrate and sequelize db:seed:all. I saw many people just creating a join table manually, but I'd like to avoid that if there's a simpler way. Code files follow (with omitted imports for some constants that are irrelevant):
model Activity
module.exports = (sequelize, DataTypes) => {
const Activity = sequelize.define('Activity', {
name: DataTypes.ENUM(cleaningActivity),
category: DataTypes.STRING,
baseRate: DataTypes.FLOAT,
specialEquipment: DataTypes.STRING,
description: DataTypes.STRING,
deleted: DataTypes.BOOLEAN
}, {});
Activity.associate = function(models) {
Activity.belongsToMany(models.ActivityBundle, {
through: 'Activity_ActivityBundle'
});
};
return Activity;
};
Activity migration:
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('Activity', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
name: {
type: Sequelize.ENUM(cleaningActivity)
},
category: {
type: Sequelize.STRING
},
baseRate: {
type: Sequelize.FLOAT
},
specialEquipment: {
type: Sequelize.STRING
},
description: {
type: Sequelize.STRING
},
deleted: {
type: Sequelize.BOOLEAN
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('Activity');
}
};
Now, ActivityBundle model:
module.exports = (sequelize, DataTypes) => {
const ActivityBundle = sequelize.define('ActivityBundle', {
name: DataTypes.ENUM(cleaningBundle),
deleted: DataTypes.BOOLEAN
}, {});
ActivityBundle.associate = function(models) {
ActivityBundle.belongsToMany(models.Activity, {
through: 'Activity_ActivityBundle'
});
};
return ActivityBundle;
};
and finally, ActivityBundle migration:
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('ActivityBundle', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
name: {
type: Sequelize.ENUM(cleaningBundle)
},
deleted: {
type: Sequelize.BOOLEAN
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('ActivityBundle');
}
};
Then, I have a seed file, where I ma trying to first create bundles, then associate them to activities, like so:
module.exports = {
up: async (queryInterface, Sequelize) => {
const bundles = await ActivityBundle.bulkCreate(bundlesArray);
return Activity.bulkCreate(activitiesArray).then(activities => {
return Promise.all(activities.map(activity => {
const bundlesPerActivity = activityBundleMapping[activity.get('name')]
.map(name => bundles.find(b => b.get('name') === name));
return activity.addBundles(bundlesPerActivity); // this method does not exist, even though it should
}));
})
},
down: (queryInterface, Sequelize) => {
}
};
Clearly I am doing something wrong. What more am I supposed to define and where? I guess the migration files need to have some mention of many to many association? Not a clue, and official documentation is incomplete imho.
You need to generate the join table yourself. Sequelize isn't that magical. :)
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable(
'Activity_ActivityBundle',
{
createdAt: {
allowNull: false,
type: Sequelize.DATE,
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE,
},
ActivityId: {
type: Sequelize.INTEGER,
primaryKey: true,
},
ActivityBundleId: {
type: Sequelize.INTEGER,
primaryKey: true,
},
}
);
},
down: (queryInterface, Sequelize) => {
// remove table
return queryInterface.dropTable('Activity_ActivityBundle');
},
};
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
How can I add data to a table in a Sequelize migration script?
This is what I got:
module.exports = {
up: function(migration, DataTypes, done) {
migration.createTable(
'person',
{
name: DataTypes.STRING,
age: DataTypes.INTEGER
},
{
charset: 'latin1' // default: null
}
);
// I want to insert person and age.
migration.insert(???);
done()
},
down: function(migration, DataTypes, done) {
migration.dropTable('queue');
done()
}
}
I figured it out. Sequelize is available from migration.migrator.sequelize. It is possible to do something like this:
up: function (migration, DataTypes, done) {
migration.createTable(
'Person',
{
name: DataTypes.STRING,
age: DataTypes.INTEGER,
}
).success(function () {
migration.migrator.sequelize.query("insert into person (name, age) values ('Donald Duck', 60)");
done();
});
},
down: function (migration, DataTypes, done) {
migration.dropTable(
'Person'
).then(function() {
done();
})
}
In the latest version of sequelize this has changed and it should now be
migration.createTable(
'Person',
{
name: DataTypes.STRING,
age: DataTypes.INTEGER,
}
).then(function () {
migration.sequelize.query("insert into person (name, age) values ('Donald Duck', 60)");
done();
});
Actually it is not a good idea to just run a query. queryInterface has create() and bulkCreate() now.
'use strict';
module.exports = {
up: function(queryInterface, Sequelize) {
return queryInterface.bulkInsert('roles', [{
label: 'user',
createdAt: new Date(),
updatedAt: new Date()
}, {
label: 'admin',
createdAt: new Date(),
updatedAt: new Date()
}]);
},
down: function(queryInterface, Sequelize) {
return queryInterface.bulkDelete('roles', null, {});
}
};
This returns a promise as expected by sequelize-cli.
Source (adapted): https://github.com/sequelize/sequelize/issues/3210
In sequelize version 4.41.2, when you use ES6+ and you want to do more complex insertions within migration you could make the up an async function which
creates your table and then inserts the necessary data into the table. To ensure that the migration either succeeds OR fails without making any changes a transaction is used for every
interaction with sequelize. Another important thing to note is that up must return a Promise.
Example of creating a table -> fetching data from other table -> modifying fetched data -> inserting modified data to new table:
module.exports = {
up: (queryInterface, Sequelize) => queryInterface.sequelize.transaction(async (transaction) => {
await queryInterface.createTable('person', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER,
},
name: {
type: Sequelize.STRING,
},
age: {
type: Sequelize.STRING,
},
}, { transaction });
// Returns array [[results], { /** result obj */ }]
const [dogs] = await queryInterface.sequelize.query('SELECT * FROM public."Dogs";', { transaction });
// prepare data
const metamorphed = dogs.map(({ name, age }) => ({
name,
age: parseInt((age * 7), 10),
}));
return queryInterface.bulkInsert('person', metamorphed, { transaction });
}),
down: queryInterface => queryInterface.dropTable('person'),
};
I'm Solution. this is an example:
"use strict";
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface
.createTable("roles", {
id: { type: Sequelize.INTEGER, autoIncrement: true, primaryKey: true },
name: { type: Sequelize.STRING },
description: { type: Sequelize.STRING },
})
.then(() => {
queryInterface.bulkInsert("roles", [{
name: "admin",
description: "Admins",
},
{
name: "company",
description: "Companies",
},
{
name: "user",
description: "Users",
},
]);
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable("roles");
},
};