I'm coding an application using Sequelize, Express and Node.JS; after set a OneToMany relationship between User and Product, just like this:
app.js:
Product.belongsTo(User, { constraints: true, onDelete: 'CASCADE' });
User.hasMany(Product);
and from a controller called admin.js I'm trying to create a new Product using a magic method:
admin.js:
exports.postAddProduct = (req, res, next) => {
const title = req.body.title;
const imageUrl = req.body.imageUrl;
const price = req.body.price;
const description = req.body.description;
req.user.createProduct({
title: title,
imageUrl: imageUrl,
price: price,
description: description
})
.then((result) => {
console.log("Record successfully created");
return res.redirect("/admin/products");
})
.catch((err) => {
console.log(err);
});
};
postAddProduct is triggered after submit a form, the error I'm getting is this:
So, my question is: based on sequelize's official documentation, after define a relationship I can use methods for create, edit o search an entity, what am I missing to get access to these methods?
thanks for your comments
even though my model is called Product, the table's name is newproducts, so, in order to solve this I made this change:
req.user.createNewproduct({
title: title,
imageUrl: imageUrl,
price: price,
description: description })
After this, problem solved
Nice to see someone else taking Max's Node.js course!
I had this same problem. For me, it stemmed from the fact that I defined how my id column works differently. Instead of this...
id: {
type: Sequelize.INTEGER,
autoIncrement: true,
allowNull: false,
primaryKey: true
},
...I did this...
id: {
type: Sequelize.UUID,
defaultValue: Sequelize.UUIDV4,
allowNull: false,
primaryKey: true,
},
and utilized the uuid package to make unique Product and User IDs. Because of this, I had to update how I defined my SQL relations in app.js:
Product.belongsTo(User, { constraints: true, onDelete: "CASCADE" });
User.hasMany(Product, { foreignKey: "id" });
So if you're using a custom ID generator or something, make sure to specify that you are in the options object and the function should appear!
You can read more about this in the docs: https://sequelize.org/docs/v6/core-concepts/assocs/#defining-the-sequelize-associations
Hope this helps!
Related
I am a beginner in NodeJs and MongoDB. I have a user schema where I have a field which is an array that is filled by the user's input value. After users enter the value, the admin also passes an array of correct answers. I want to create a function which runs on all users array field and on correct answer store the score in users schema. Just wanted to know how do I run the function on all entries of the collection.
//Final result schema by the admin
const resultSchema = new mongoose.Schema({
matchday:Number,
homeTeam:String,
awayTeam:String,
utcDate:Date,
finalUpdateTime:Date,
result:Array
})
//The predicted answer Schema
const predictSchema = new mongoose.Schema({
user:{
type:mongoose.Schema.ObjectId,
ref:'User',
required:[true, 'Predicted Team must belong to a User']
},
teamData:Array,
matchday: Number,
score:{
type:Number,
default:0
},
createdAt: {
type:Date,
default:Date.now()
},
lastUpdated:Date,
},{
toJSON: {
virtuals: true,
},
toObject: {
virtuals: true,
},
})
You can define a static method for your schema. Statics are methods that can be invoked directly by a Model.
See here
You can pass array of correct answers to this method and check the answers for each user in your collection. You can retrieve all users using Find
I managed to solve the issue and it works but not sure if its the correct way to do it
exports.updateUserScore = async (req, res, next) => {
const user = await Predict.find({ matchday: req.body.matchday });
user.map(async (el) => {
let score = 0;
el.teamData.map((e) => {
if (req.body.teamData.includes(e)) score = score + 1;
});
console.log(score, el._id);
await Predict.findByIdAndUpdate(el._id, { score: score });
});
res.status(200).json({
status: 'success',
message: 'Updated User Score Successfully',
});
};
I'm building a news website, and I this mongoose schema:
let mongoose = require('mongoose');
let articleSchema = mongoose.Schema({
image1:{
type: String,
required: true
},
title:{
type: String,
required: true
},
author:{
type: String,
required: true
},
date:{
type: String,
required: true
},
updated:{
type: String,
default: 'not updated'
},
title_nd:{
type: String,
required: false
},
body:{
type: String,
required: true
},
comments: [commentsSchema],
likes:{ type:Number, default:0 }
});
let Article = module.exports = mongoose.model('Article', articleSchema);
And I want to add a form so users can add their comments.
The question is how do I create a new schema for comments and link it to article schema, and then if the user adds a comment the comment added to the database and then shows on the article comment section?
Modeling a separate schema for comment is not a good idea in my humble opinion, since it is a classic case of one to few mapping which is an ideal use case for embedding the document. To give you a basic idea about data modeling i am quoting here
You need to consider two factors:
Will the entities on the āNā side of the One-to-N ever need to stand alone?
What is the cardinality of the relationship: is it one-to-few; one-to-many; or one-to-squillions?
Based on these factors, you can pick one of the three basic One-to-N schema designs:
Embed the N side if the cardinality is one-to-few and there is no need to access the embedded object outside the context of the parent object
Use an array of references to the N-side objects if the cardinality is one-to-many or if the N-side objects should stand alone for any reasons
Use a reference to the One-side in the N-side objects if the cardinality is one-to-squillions
Please refer to a very well written and articulated post 6 Rules of Thumb for MongoDB Schema Design: Part 1 from mongodb blogs.
Even after this if you think it is a good idea to link to another schema please refer to this SO question - Referencing another schema in Mongoose
so I found a solution for this:
// :id is all articles with all ids
router.post('/:id', function (req, res) {
let comment = {};
comment.body = req.body.body;
comment.user = req.user;
comment.date = new Date(Date.now()).toDateString();
// Express validator
req.checkBody('body').len(5, 100);
let errors = [];
errors = req.validationErrors();
if(errors) {
Article.findById(req.params.id, function (err, article) {
if(err)throw err;
req.flash('danger', 'Body minimum length is 5 and maximum 100!');
res.redirect('/articles/'+article.id);
});
} else {
Article.findById(req.params.id, function (err, article) {
if(err)throw err;
article.comments.push({'body':comment.body,'user':comment.user,'date':comment.date});
article.save(function (err) {
if (err) {
throw err;
}else {
req.flash('success', 'Comment added!');
res.redirect('/articles/'+article.id);
}
});
});
}
});
EDIT: code above in more readable form:
router.post('/:id', async (req, res) => {
let article = await Article.findById(req.params.id);
if (!article) res.status("403");
let articleUrl = "/articles/${article.id}";
let comment = {
body: req.body.body,
user: req.user,
date: new Date(Date.now()).toDateString();
};
if (commment.body.lengh >= 100 || comment.body.length <= 5) {
req.flash('danger', 'Body minimum length is 5 and maximum 100!');
return res.redirect(articleUrl);
}
articles.comments.push(comment);
await article.save();
req.flash('success', 'Comment added!');
res.redirect(articleUrl);
});
Users are able to post items which other users can request. So, a user creates one item and many users can request it. So, I thought the best way would be to put an array of users into the product schema for who has requested it. And for now I just want to store that users ID and first name. Here is the schema:
const Schema = mongoose.Schema;
const productSchema = new Schema({
title: {
type: String,
required: true
},
category: {
type: String,
required: true
},
description: {
type: String,
required: true
},
userId: {
type: Schema.Types.ObjectId,
ref: 'User',
required: true
},
requests: [
{
userId: {type: Object},
firstName: {type: String}
}
],
});
module.exports = mongoose.model('Product', productSchema);
In my controller I am first finding the item and then calling save().
exports.postRequest = (req, res, next) => {
const productId = req.body.productId;
const userId = req.body.userId;
const firstName = req.body.firstName;
const data = {userId: userId, firstName: firstName};
Product.findById(productId).then(product => {
product.requests.push(data);
return product
.save()
.then(() => {
res.status(200).json({ message: "success" });
})
.catch(err => {
res.status(500).json({message: 'Something went wrong'});
});
});
};
Firstly, is it okay to do it like this? I found a few posts about this but they don't find and call save, they use findByIdAndUpdate() and $push. Is it 'wrong' to do it how I have done it? This is the second way I tried it and I get the same result in the database:
exports.postRequest = (req, res, next) => {
const productId = req.body.productId;
const userId = req.body.userId;
const firstName = req.body.firstName;
const data = {userId: userId, firstName: firstName};
Product.findByIdAndUpdate(productId, {
$push: {requests: data}
})
.then(() => {
console.log('succes');
})
.catch(err => {
console.log(err);
})
};
And secondly, if you look at the screen shot is the data in the correct format and structure? I don't know why there is _id in there as well instead of just the user ID and first name.
Normally, Developers will save only the reference of other collection(users) in the collection(product). In addition, you had saved username also. Thats fine.
Both of your methods work. But, second method has been added in MongoDB exactly for your specific need. So, no harm in using second method.
There is nothing wrong doing it the way you have done it. using save after querying gives you the chance to validate some things in the data as well for one.
and you can add additional fields as well (if included in the Schema). for an example if your current json return doesn't have a field called last_name then you can add that and save the doc as well so that's a benefit..
When using findById() you don't actually have the power to make a change other than what you program it to do
One thing I noticed.. In your Schema, after you compile it using mongoose.modal()
export the compiled model so that you can use it everywhere it's required using import. like this..
const Product = module.exports = mongoose.model('Product', productSchema);
I have a user model in Sequelize for a Postgres db:
var User = sequelize.define('User', {
fb_id: DataTypes.STRING,
access_token: DataTypes.TEXT,
first_name: DataTypes.STRING,
last_name: DataTypes.STRING,
email: DataTypes.TEXT,
profilePictureURL: DataTypes.TEXT,
library: DataTypes.ARRAY(DataTypes.STRING)
}, {
underscored: true,
classMethods: {
associate: function(models) {
}
}
});
I am trying to update the library field by adding ISBNs to the array. This is the code for my POST request:
req.user.library.push(req.body._isbn); // adding the posted ISBN to the user object in my express-session
User.findOrCreate({where: {fb_id: req.user.fb_id},
defaults: {
access_token : req.user.access_token,
first_name : req.user.first_name,
last_name : req.user.last_name,
email : req.user.email,
profilePictureURL : req.user.profilePictureURL,
library: req.user.library // new library object
}})
.spread(function (updatedUser, created){
res.status(200).json(updatedUser);
}).error(function(err){
res.status(500).json(err);
});
There is no error, but the library field is not updated after checking the updatedUser object. How do I correctly update an array field in Sequelize?
For next visitors, I may have found a better way to solve this issue :
User.update(
{library: Sequelize.fn('array_append', Sequelize.col('library'), req.body._isbn)},
{where: {fb_id: req.user.fb_id}}
);
I ran into this before and found the answer deep in their Github issues. The way I accomplished it is
User.find({
where: {
fb_id: req.user.fb_id
}
})
.then((user) => {
user.library.push(req.body._isbn)
user.update({
library: user.library
},{
where: {
fb_id: req.user.fb_id
}
})
.then(user => res.json(user))
})
It definitely feels like there is a better way, but this way how I found a way.
Kinda old issue but I found a workaround that could help in the future, from what I understand sequelize may not recognize an updated data as new instance so it will consider it as local scope only. Solution for me was to create a new object upon the needed array, something like:
let newArray = Object.assign([], instance.arrayToUpdate);
newArray.push(myInterestingData)
await instance.update({
arrayToUpdate: newArray
});
I use Sequelize for my Server (with mysql dialect); in Sequelize's documentation is written that this:
var Task = this.sequelize.define('Task', { title: Sequelize.STRING })
, User = this.sequelize.define('User', { username: Sequelize.STRING })
User.hasMany(Task)
Task.belongsTo(User)
creates automatically foreign key references with constraints;
but for me this doesn't happen:
var Shop = sequelize.define('Shop', {
name: Sequelize.STRING,
address: Sequelize.STRING,
phone: Sequelize.STRING,
email: Sequelize.STRING,
percentage: Sequelize.FLOAT,
text: Sequelize.TEXT,
categories: Sequelize.TEXT,
start: Sequelize.DATE,
end: Sequelize.DATE
});
var Offer = sequelize.define('Offer', {
name: Sequelize.STRING,
deadline: Sequelize.DATE,
optionDuration: Sequelize.INTEGER
});
Shop.hasMany(Offer);
Offer.belongsTo(Shop);
This creates the two tables shops and offers, both of them with only "id" primary key
I also have some n:m associations like:
Group.hasMany(Accesslevel);
Accesslevel.hasMany(Group);
but also in this case, in the join table that Sequelize creates, there are no foreign key;
so if I delete for ex. an acccesslevel, than the corresponding records in the join table accesslevelsgroups are not deleted.
Does anybody know if I'm doing something wrong or missing something?
What I need is to create all the foreign keys for the associations and the possibility to specify the behaviour 'onDelete' and 'onUpdate' (cascade)
-- UPDATE
I've created a route for executing sync:
myServer.get('/sync', function (req, res) {
sequelize.sync({force: true}).success(function() {
console.log('sync done');
res.send(200, 'sync done');
}).error(function(error) {
console.log('there was a problem');
res.send(200, 'there was a problem');
});
});
So then in the browser I type 127.0.0.1:port/sync to create the db structure
Did you add the relation after creating the table ( in the database ) ?
You need .sync({ force: true }) if you modify your scheme's and run your program again.
Sequelize will only create the table and all it's references if the table does not exist yet in the database.
Are you calling sync after all associations have been made ?
Are you using InnoDB ?
Oddly enough I can reproduce that, The easiest fix is to define the key manually I think, that's how I solved it, Might be a bug otherwise I'm not sure.
See here:
http://sequelizejs.com/docs/latest/associations#block-3-line-24
You should give foreign keys like that, it is not related to sync.
Offer.associate = function (models) {
models. Offer.belongsTo(models. Offer, {
onDelete: "CASCADE",
foreignKey: 'shopId',
targetKey: 'id'
});
};
This question was actually answered here
As trivial answers are converted to comments and it is hard to notice them under question I'll duplicate its main point here.
To add references constraints to the columns, you can pass the options onUpdate and onDelete to the association calls. . . .
User.hasMany(Task, { onDelete: 'SET NULL', onUpdate: 'CASCADE' })