I'm building Full Stack, Social media app using Sequelize ORM.
sequelize: 6.6.5
sequelize-cli: 6.2.0
My database is built of tables Users, Posts and ReadPosts - which has two foreign keys - UserId and PostId.
One of the features of the app, is that user can easily see new posts, that have not been read by him/her yet, so every time user reads a post it generates a row in ReadPost table, made of UserId (who read the post) and PostId (which was read by the user).
What I'm trying to do now, is to display all posts that have not been read, so it would be some kind of excluding left join, that would get all existing posts, and exclude those pots from ReadPost with given userId, but I can't figure out how to do it with Sequlize.
ReadPost model:
module.exports = (sequelize) => {
const readPost = sequelize.define("ReadPost", {})
readPost.associate = models => {
readPost.belongsTo(models.User, {
foreignKey: {
allowNull: false
},
onDelete: "CASCADE",
foreignKeyConstrains: true
})
readPost.belongsTo(models.Post, {
foreignKey: {
allowNull: false
},
onDelete: "CASCADE",
foreignKeyConstrains: true
})
}
return readPost
}
I know I could do it virtually and just run findAll() on posts, and not display those that have not been read yet, depending on some javascript flag or simply class, but this is a project for my portfolio so I want to do it properly. Can I have some help please?
#Anatoly
I had to play a bit with your code, as I'm using newer version of sequelize and got something like this:
exports.showAllUnreadPosts = (req, res, next) => {
db.Post.findAll({
where: {
"ReadPost.id": null,
userId: res.locals.userId //userId extracted from authorization middleware
},
include: [{
model: db.ReadPost,
required: false,
attributes: []
}]
})
with that it retuns
"error": "Error SequelizeDatabaseError: Unknown column 'Post.ReadPost.id' in 'where clause'"
I tried to understand the line '"ReadPost.id": null', but as far as I understand sql syntax it would be looking for that column in Post table? I don't have such column, relation exists in ReadPost table, where it gathers userIds and postIds, not sure if my implementation is clear
Just in summary - I need to get all existing posts from Post table and compare it with ReadPost table, where postId and userId are stored. So probably I'd have to run findAll on Posts, findAll on ReadPost with current userId, and exclude all those postIds recorded in ReadPost from Post.findAll
Have a look screenshoot of how currently data looks like in those tables:
picture of DB tables
So baisically I need Post.findAll() + ReadPost.findAll() where userId: res.locals.userId and return all posts from Post table but do not exist with that ReadPost query.
I hope that makes it more clear.
#Anatoly 11/03/22
Query works now, but returns only posts that have not been read by ANY user (row doesn't exist) and the user is the author of the post.
What I managed to do for now, is get all posts that have been read by the user (code below). I need exact opposite of it (row doesn't exist or it exists, but not with this userId)
So, from all posts that exist, Select those read by user, and exclude those else from all of the posts in the DB
exports.showAllUnreadPosts = (req, res, next) => {
db.Post.findAll({
where: {
'$readposts.UserId$': res.locals.userId // User Id extracted from auth middleware
},
include: [{
model: db.ReadPost,
required: false,
attributes: [],
}]
}).then(unreadPosts => {
res.status(200).json(unreadPosts);
})
.catch((error) => {
res.status(500).json({
error: 'Error ' + error
})
})
}
Can you please advise?
Right, it seems like I found solution with a great help from #Anatoly.
I'm not sure, if it's a good idea, since I added a second method in the THEN block, I'm happy to get any feedback on it.
exports.showAllUnreadPosts = (req, res, next) => {
db.Post.findAll({
where: {
'$readposts.UserId$': res.locals.userId // User Id extracted from auth middleware
},
attributes: ['id'],
include: [{
model: db.ReadPost,
required: false,
attributes: [],
}]
}).then(readPosts => {
db.Post.findAll({
where: {
id: {
[Op.not]: (() => readPosts.map(readPost => readPost.id))()
}
}
})
.then((unreadPosts) => {
res.status(200).json(unreadPosts);
})
.catch((error) => {
res.status(500).json({
error: 'Error' + error
})
})
})
.catch((error) => {
res.status(500).json({
error: 'Error ' + error
})
})
}
First of all, there is a method that checks all the posts that are already read by the user in readpost table and returns post ids. Secondly in the THEN block, it gets all the existing posts in the db, and excludes those with ids from above method (by [OP.not]). I hope it makes sense, not sure about performance.
You can query all posts that doesn't have a link record in ReadPost by adding a condition Readpost.id is null like this:
const unreadPosts = await Post.findAll({
where: {
'$ReadPost.id$': null,
userId: userId
},
include: [{
model: ReadPost,
required: false,
attributes: [],
where: {
UserId: userId
},
}]
})
And of course, you need to add an association from Post to ReadPost:
Post.hasMany(ReadPost, <some_options_here>);
Related
I have this relationship
User -> Cartlists <- Product
I am trying to show the products I have in my cartlists by querying it like this using the where function
router.get('/cart', ensureAuthenticated, async function (req, res) {
let cartlists = await Cartlist.findAll()
Product.findAll({
include: User,
where: { productId: cartlists.productProductId },
raw: true
})
.then((cartlists) => {
res.render('checkout/cart', { cartlists });
})
.catch(err =>
console.log(err));
})
However, when I debugged it, it shows me that cartlists.productProductId is undefined even though there is a value in mySQL. I also get an error message saying
'WHERE parameter "productId" has invalid "undefined" value'
Does anyone know why it gives me an undefined value even though there are indeed values in MySQL table and how I might be able to fix this? Or how can I query just the products I have added to my cartlists table?
I have been using the following tutorial to learn how to implement one-to-many relationship in Sequelize where a Tutorial has many Comment and Comment belongs to a Tutorial: https://www.bezkoder.com/sequelize-associate-one-to-many/
While I have the code modeling what is documented for setting up the relationship between the two models, I receive the following error during creating the Comment model:
Unknown column 'tutorialId' in 'field list'
Additionally, I receive the following SQL output:
Executing (default): INSERT INTO `comments` (`id`,`name`,`text`,`createdAt`,`updatedAt`,`tutorialId`) VALUES (DEFAULT,?,?,?,?,?);
app.js
const tutorialsRouter = require('./routes/api/tutorials');
const commentsRouter = require('./routes/api/comments');
app.use('/api/tutorials', tutorialsRouter);
tutorialsRouter.use('/:tutorialId/comments', commentsRouter);
/models/index.js
db.tutorials = require("./tutorial.model")(sequelize, Sequelize);
db.comments = require("./comment.model")(sequelize, Sequelize);
db.tutorials.hasMany(db.comments, { as: "comments" });
db.comments.belongsTo(db.tutorials, {
foreignKey: "tutorialId",
as: "tutorial",
});
/models/comment.model.js
module.exports = (sequelize, DataTypes) => {
const Comment = sequelize.define('comment', {
name: {
type: DataTypes.STRING,
},
text: {
type: DataTypes.STRING,
},
});
return Comment;
}
/routes/comments.js
const comments = require('../../controllers/comments.controller');
const router = require('express').Router({ mergeParams: true });
router.post('/', comments.create);
module.exports = router;
/controllers/comments.controller.js
const db = require('../models');
const Comment = db.comments;
exports.create = (req, res) => {
...
Comment.create({
name: req.body.name,
text: req.body.text,
tutorialId: req.params.tutorialId,
})
.then( ... )
.catch( ... );
}
Then in Postman I receive a 500 (of the error message above) when issuing the request:
POST localhost:3000/api/tutorials/1/comments
{
"name": "John Doe",
"text": "Lorem ipsum..."
}
I don't think I should have to define a tutorialId field on the Comment model. Grr...
This may be very obvious to some of you, but it's tripping me up trying to learn. Any help is very much appreciated. :)
The issue that you're having is a result of using aliases via the as property. See the docs for belongsTo and hasMany. Here's a code sample that performs the inserts without the error.
let {
Sequelize,
DataTypes,
} = require('sequelize')
async function run () {
let sequelize = new Sequelize('dbname', 'username', 'password', {
host: 'localhost',
port: 5555,
dialect: 'postgres',
logging: console.log
})
let Comment = sequelize.define('comment', {
name: {
type: DataTypes.STRING,
},
text: {
type: DataTypes.STRING,
},
})
let Tutorial = sequelize.define('tutorial', {
title: {
type: DataTypes.STRING,
},
content: {
type: DataTypes.STRING,
}
})
Tutorial.hasMany(Comment)
Comment.belongsTo(Tutorial)
// This just recreates the tables in the database.
// You would really only want to use a force sync
// in a development environment, since it will destroy
// all of the data....
await sequelize.sync({ force: true })
let tutorial = await Tutorial.create({
title: 'Tutorial',
content: 'Hmm....'
})
let comment = await Comment.create({
name: 'Comment',
text: 'Something, something....',
tutorialId: tutorial.id,
})
await sequelize.close()
}
run()
Edit
This is just an edit to my original answer above. The OP Tom Doe discovered that the issue was being caused by a mismatch between the definitions of the tables in the database and the models defined via sequelize (see comments below). As we discovered, one way to troubleshoot the mismatch is to force sync a new version of the database, and then compare the new version of the database with the original version. There may be differences in the definitions of the columns or the contraints. Force syncing the database can be done via the command
await sequelize.sync({ force: true})
Important Note: The above statement will overwrite the existing database and all of its data. See the docs for more information.
I am receiving the error when I make a call to my API with a get request:
Include unexpected. Element has to be either a Model, an Association or an object.
My Models look like this:
module.exports = (sequelize, Sequelize) => {
const Productions = sequelize.define("productions", {
id: {
type: Sequelize.SMALLINT,
autoIncrement: true,
primaryKey: true
},
setupTime: {
type: Sequelize.DECIMAL(6, 3)
},
notes: {
type: Sequelize.TEXT
}
}, { timestamps: false });
return Productions;
};
module.exports = (sequelize, Sequelize) => {
const ProductionPrints = sequelize.define("productionPrints", {
id: {
type: Sequelize.SMALLINT,
autoIncrement: true,
primaryKey: true
},
compDate: {
type: Sequelize.DATE
}
}, { timestamps: false });
return ProductionPrints;
};
The relationship between the models is defined here:
db.productions = require("./productions.model.js")(sequelize, Sequelize);
db.productionprints = require("./production-prints.model.js")(sequelize, Sequelize);
db.productions.hasOne(db.productionprints, {
foreignKey: {
name: 'productionId',
allowNull: false
}
});
db.productionprints.belongsTo(db.productions, { foreignKey: 'productionId' });
And the sequelize query looks as so:
const db = require("../models");
const Productions = db.productions;
const ProductionPrints = db.productionPrints;
exports.findAll = (req, res) => {
Productions.findAll({
include: [ { model: ProductionPrints, as: 'prints' } ]
})
.then(data => {
res.send(data);
})
.catch(err => {
res.status(500).send({
message:
err.message || "An error occurred while finding the productions."
});
});
};
I have checked around for others with the issue but have had no avail with any solutions posted on those problems. Generally it was caused by typos, or error in the require paths. I have checked those and all my other includes work, just not on any of the models I include on the productions model.
Any feedback is greatly appreciated.
Error was being caused by a typo:
db.productions = require("./productions.model.js")(sequelize, Sequelize);
db.productionprints = require("./production-prints.model.js")(sequelize, Sequelize);
when this was being referenced in the assigned to a constant:
const Productions = db.productions;
const ProductionPrints = db.productionPrints;
shame on me for changing my case use:
db.productionprints != db.productionPrints
I had the same issue , this is usually caused by naming issue , to track the issue you can check one of the following places to resolve it
check if you are calling the correct model class name
when importing models becarefull not to call the file name instead of model name => the one exported
3.check if you got your association correctly by calling the exported model not the file name
check if your cases e.g users vs Users.
a bonus tip is to use same name for model and file name to avoid these issues because the moment you make them different you likely to make these mistakes
Following the answer of Kelvin Nyadzayo, i have the model.findOne(options) method with a
options.include like this:include: [ { } ] in the options parameter
The include has to have the proper syntax: [{model: Model, as: 'assciationName'}]
And the mine was empty
So this, was triggering the same error
I'm running ExpressJS with Sequelize/MySQL and trying very hard to get a simple validator check working for unique email address.
Here is my user model. And for the life of me I don't understand why this is allowing records that have duplicate email address. Surely the email.unique=true would be preventing this.
'use strict';
module.exports = (sequelize, DataTypes) => {
var User = sequelize.define('User', {
firstName: DataTypes.STRING,
lastName: DataTypes.STRING,
email: {
type: DataTypes.STRING,
allowNull: false,
unique: true,
validate: {
isEmail: {
msg: "Must be a valid email address",
}
}
}
}, {
indexes: [{
fields: ['email'],
unique: true,
}]
});
return User;
};
Any help appreciated.
EDIT:
As requested here is the controller code for create user.
const User = require('../models').User;
exports.create = (req, res) => {
User.create( req.body )
.then( user => {
res.json( user );
})
.catch( errors => {
res.json({ errors: errors.errors });
});
};
One way to solve this is by using sequelize.sync() to create your table according to the schema specified in the model if the table exists then you should pass {force: true} to the sync method, the table will be dropped and a new one will be created.
though using sequelize.sync() is not highly recommended especially in production due to issues with migration files etc, you can google than for more details.
` User.update({
role: 'user'
}, {
where: {
email: email
}
}).then(function(result) {
//Returned value: either [0] or [1] i.e. record is updated or not
});
`
This is sequelize update query where User is model (user's role is updated by email). Its working fine but I want that using update query, I will get user's data as well. Is it possible to get it without using another query ?
Nope, not possible without another query. This isn't a limitation of Sequelize, it's a limitation of SQL. Under the hood, sequelize needs to do:
update users set role = 'user' where email = 'test#example.com'
which will just update the rows, but will not retrieve any rows. Then, it needs to do a second:
select * from users where email = 'test#example.com'
to actually get the (now updated) rows.
If you are using postgresql then you can set returning true after where clause
where: { socketID: socket.id },
returning: true,
and and if you are using other db like mysql as in my case then you cant to it in a single hit you have to use anther query like below
async function PersonalDetail(req, res) {
var Email = req.body.email;
const user = await User.findOne({ where: { EmailAddress: Email, IsActive: true } });
if (user != null) {
user.UpdateDate = new Date();
user.EmplomentID = req.body.EmplomentID;
user.LivingID = req.body.LivingID;
user.LocationID = req.body.LocationID;
await user.save();
res.json({
IsSuccess: true,
message: 'User updated',
User: user
});
}
else {
res.json({
IsSuccess: true,
message: 'User Not updated',
User: ""
});
}
}