I have a problem with populate. I made a query to get User, Project and Topic information (Those are my 3 models). I need to show multiples dates in profile view. This is my code:
Project.js:
module.exports = {
attributes: {
name: {
type: "string"
},
topics: {
collection: "topic",
via: "projects"
},
members: {
collection: "user",
via: "projects"
},
content: {
collection: "content",
via: "projectData"
}
}
};
Topic.js:
module.exports = {
attributes: {
name: {
type: "string"
},
projects: {
collection: "project",
via: "topics"
}
}
};
in User.js:
show: function(req, res, next) {
User.findOne({id: req.session.User.id}).populateAll().exec(function prjFound(err, user){
if (err) return next(err);
if (!user) return next();
console.log(user);
res.view({
user: user
});
});
},
Console print this:
{ projects:
[ { name: 'Fran',
createdAt: '2017-06-19T21:33:17.152Z',
updatedAt: '2017-06-19T21:33:17.190Z',
id: 97 },
{ name: 'River Plate',
createdAt: '2017-06-19T21:36:38.757Z',
updatedAt: '2017-06-19T21:36:38.798Z',
id: 98 },
{ name: 'Guido',
createdAt: '2017-06-20T01:33:53.843Z',
updatedAt: '2017-06-20T01:33:53.926Z',
id: 99 } ],
group: [],
mat: 222222,
name: 'Francisco',
lastname: 'P',
email: 'fran#1.com.ar',
encryptedPassword: '$2a$10$nKp/eAOCDPw4BS.PvQCThe42wa2/8ZABw4JzA0no9GPVT4VjFl3ZO',
createdAt: '2017-06-19T21:32:10.535Z',
updatedAt: '2017-06-19T21:32:10.535Z',
id: '594842da6aeecd880ebab4e6'
}
I want to get all atributes of project model (Content, topic, and members), not only the name and id.
Anyone can explain Why my code is wrong?
Sails/Waterline populate/populateAll do 1 level of population. For 2 or deeper level you need to write code for it.
E.g. Gather ids of user's project and do populateAll on Project.find
Sailsjs doesn't currently support population within a populated field. Write a query in the returned response and append it to the field that you want to populate, send the response with your desired results.
Check this.
let result = await model.find(filter).populate("fieldName", {select:['attribute1','attribute1']})
Related
I have an array of reviews, I want to add a review using addToSet that will check if user is present in the array, then we do not want to add since one user can only review once.
My schema looks like this:
const sellerSchema = new mongoose.Schema({
user: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
unique: true,
},
reviews: [
{
by: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
unique: true,
},
title: {
type: String,
},
message: {
type: String,
},
rating: Number,
imagesUri: [{ String }],
timestamp: {
type: Date,
default: Date.now,
},
},
],
});
I might be doing the query wrong, but can't figure out how to add a review and check if current user has not reviewed before.
Here is the query where I add the review:
router.post("/review/:_id/", async (req, res) => {
try {
const stylist_id = mongoose.Types.ObjectId(req.params._id);
const review = {
by: req.user._id,
title: req.body.title,
message: req.body.message,
rating: parseFloat(req.body.rating),
};
if (req.body.imagesUri) {
//if there is images, we need to set it up
review.imagesUri = req.body.imagesUri;
}
await Seller.updateOne(
{ _id: seller_id },
{ $addToSet: { reviews: review } } //get the review that matches to the user_id
);
return res.status(200).send(true);
}catch(err){
res.status(502).send({
error: "Error creating a review.",
});
}
});
I'm thinking of checking for seller's id and also check that no review is by current user, but it is not working.
const userID = req.user._id;
await Seller.updateOne(
{ _id: seller_id, reviews: { $elemMatch: { by: { $ne: { userID } } } } },
{ $addToSet: { reviews: review } } //get the review that matches to the user_id
);
ANSWER:
I was able to solve the issue, in case other people have same issue. I did this:
await Seller.updateOne(
{
_id: seller_id,
"reviews.by": { $nin: [req.user.id] },
//knowing req.user._id is a mongoose.Types.ObjectId.
//You can also use [id1, id2, ...] to the array to check for other id's
},
{ $addToSet: { reviews: review } } //get the review that matches to the user_id
);
Here is the documentation for $nin operator: https://www.mongodb.com/docs/manual/reference/operator/query/nin/
You are pushing the review object inside an object.
Instead do this:
await Seller.updateOne(
{ _id: seller_id },
{ $addToSet: { reviews: review } }
);
I am trying to populate my ChatRoom model with the User reference. However, it returns a ChatRoom object with only _ids where I expected usernames, as if I never applied populate on it.
Here is an extract of my ChatRoom model :
const ChatRoom = mongoose.model("ChatRoom", {
sender: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
},
roomname: { type: String, default: "new room" },
messages: [
{
messages: {
type: mongoose.Schema.Types.ObjectId,
ref: "Message",
},
meta: [
{
user: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
},
delivered: Boolean,
read: Boolean,
},
],
},
],
participants: [
{
user: { type: mongoose.Schema.Types.ObjectId, ref: "User" },
},
],
isPrivate: { type: Boolean, default: "false" },
});
My User model :
const User = mongoose.model("User", {
username: { required: true, unique: true, type: String },
avatar: Object,
token: String,
hash: String,
salt: String,
chatroom: {
type: mongoose.Schema.Types.ObjectId,
ref: "ChatRoom",
},
});
As this seems to be a recurrent issue, I tested several StackOverflow answers for my populate code :
Using populate('participants.user') and 'model: user' or just populate('participants.user'), same solution here:
const chatroom = await ChatRoom.findById(req.params.id)
.populate([
{
path: "participants.user",
model: "User",
},
])
.exec((err, user) => {
if (err) {
console.log("error", err);
} else {
console.log("Populated User " + user);
}
});
The console.log returns :
Populated User { _id: new ObjectId("62262b342e28298eb438d9eb"),
sender: new ObjectId("6225d86c9340237fe2a3f067"), roomname:
'Hosmeade', participants: [ { _id: new
ObjectId("6225d86c9340237fe2a3f067") } ], isPrivate: false,
messages: [], __v: 0 }
As if no populate method was ever applied. On the client side, I get an empty string.
Checking my documents aren't empty, this link mentions that Mongoose get problems with detecting referring model across multiple files but the solution doesn't work for me :
_id:6225d86c9340237fe2a3f067 username:"Berlioz" token:"rTCLAiU7jq3Smi3B"
hash:"wvJdqq25jYSaJjfiHAV4YRn/Yub+s1KHXzGrkDpaPus="
salt:"hZdiqIQQXGM1ryYK" avatar:Object
__v:0
If I remove the .exec(...) part, I get the info on the client side, but participants is still filled with only id :
chatroom response : Object { _id: "62262bb14e66d86fb8a041e8",
sender: "6225d86c9340237fe2a3f067", roomname: "Very secret room",
participants: (1) […], isPrivate: false, messages: [], __v: 0 }
I also tried with select: 'username' and get the same result as above :
const chatroom = await ChatRoom.findById(req.params.id).populate({
path: "participants.user",
select: "username",
});
Populating it "as an array"
Changing type of participants.user in my ChatRoom model into an Object (nothing changes)
If needed hereafter are my repos:
Client side and Backend
I run out of ideas on how to debbug my code. Any help would be great !
These are the blog objects in the Database:
[
{
_id: 5fec92292bbb2c32acc0093c,
title: 'Boxing ring',
author: 'T. Wally',
content: 'boxing stuff',
likes: 0,
user: {
_id: 5fd90181d1e88a13109433f9,
username: 'Johnny_23',
name: 'John Q'
},
__v: 0
},
{
_id: 5fec9481ce9c4a47a0ca6a2a,
title: 'Football OP',
author: 'RQ',
content: 'it`s football',
likes: 2,
user: {
_id: 5fec942dce9c4a47a0ca6a29,
username: 'malcomm',
name: 'Malcom'
},
__v: 0
}
]
And i want to only find and show the blogs that belong to a certain user
listRouter.get('/', async (request, response) => {
const blogs = await Blog
.find({}).populate('user', { username: 1, name: 1 })
response.json(blogs)
})
how can i access to the user id of every object and use it in a way that i can match the blogs?
Since property user has _id, you can construct your query like this:
const blogs = await Blog.find({ user: { _id: '_id of a certain user' } })
There will be a case where field user is a reference (its value is an ObjectId). In this case, try
const blogs = await Blog.find({ user: '_id of a certain user' }).populate('user', { username: 1, name: 1 })
When you are posting a blog you are taking the user id as filed in data. You can also send the blog id to user collection so that it will be easy to populate data from user collection.
//push blogid to user blogsId filed
await UserModel.updateOne({ _id: req.body.userId }, {
$push: {
blogsId: blog._id
}
})
You have to take a field in schema blogsId or whatever you want as an array field.
What I'm looking for is an instance method in Model that will return only the attributes of that model & exclude instances of any included model.
eg: Imagine I have 2 models, with a hasMany ( or any ) association:
Post {
id,
content,
user_id
}
User: {
id,
name,
}
and I have:
const userWithPosts = await User.findOne({
where: { id: 33 },
include: [{
model: Post,
as: 'posts'
}]
});
console.log(userWithPosts)
/*
{
id: 33,
name: 'John Doe',
posts: [
Post {
id: 1,
content: '..',
user_id: 33
},
Post {
id: 2,
content: '...',
user_id: 33
}
]
}
*/
I'm looking for a method, say getOwnAttributes or something like that which does:
userWithPosts.getOwnAttributes()
/*
{
id: 33,
name: 'John Doe',
}
*/
I've looked into couple of things:
userWithPosts.get({ raw: true })
userWithPosts.get({ plain: true })
userWithPosts.toJSON()
All of the above returns included instances as well.
Any existing method or workaround that can do this?
EDIT: I'm not talking about doing it at query time, but getting the value from already queried instance. Currently my work-around for this is:
const payload = _.pick(userWithPosts.toJSON(), [
...Object.keys(User.rawAttributes),
]);
You can refer to the code below to exclude attributes of Post table.
const userWithPosts = await User.findOne({
where: { id: 33 },
include: [{
model: Post,
as: 'posts',
attributes: []
}]
});
I hope it helps!
I'm trying to update a value inside my array of objects.
Looking at the above mongoDB schema what I want is:
Find an expense with the ID match with the _id and need to update the fields with new ones from the req.body.
Just need to update the: expensesType, description, price and status.
The following code is what I tried to do.
First I need to match the right expense and it works fine but when I try to house.save() show me a message 'house.save is not a function'. So I think maybe I need to use a mongoDB function to get the result.
router.put("/editExpense/:id", ensureAuthenticated, (req, res) => {
var id = mongoose.Types.ObjectId(req.params.id);
House.find(
{ "expensesHouse._id": id },
{
members: 1,
name: 1,
description: 1,
address: 1,
type: 1,
user: 1,
userID: 1,
userType: 1,
expensesHouse: { $elemMatch: { _id: id } },
date: 1
}
).then(house => {
console.log(house);
expenseType = req.body.expenseType;
description = req.body.description;
price = req.body.price;
status = req.body.status;
house.save().then(() => {
req.flash("success_msg", "Expenses Updated");
res.redirect("/houses/dashboard");
});
});
});
****** UPDATED ******
After a search I found this updateOne and after adjusts, this is my final result but this way I delete every record..
router.put("/editExpense/:id", ensureAuthenticated, (req, res) => {
var id = mongoose.Types.ObjectId(req.params.id);
House.updateOne(
{ "expensesHouse._id": id },
{
members: 1,
name: 1,
description: 1,
address: 1,
type: 1,
user: 1,
userID: 1,
userType: 1,
expensesHouse: { $elemMatch: { _id: id } },
date: 1
},
{ $set: { "expensesHouse.expenseType": req.body.expenseType } }
).then(house => {
req.flash("success_msg", "Expenses Updated");
res.redirect("/houses/dashboard");
});
});
*********** RESOLUTION ***********
I just fixed the problem the way I show below.
House.updateOne(
{ "expensesHouse._id": id },
{
$set: {
expensesHouse: {
expenseType: req.body.expenseType,
description: req.body.description,
price: req.body.price,
status: req.body.status
}
}
}
You are really close to the answer the problem right now that you are having is syntax difference between find and UpdateOne
This is what Find expects, Check MongoDB docs
db.collection.find(query, projection)
This is what updateOne expects, Check Mongo docs
db.collection.updateOne(
<filter>,
<update>,
{
upsert: <boolean>,
writeConcern: <document>,
collation: <document>,
arrayFilters: [ <filterdocument1>, ... ],
hint: <document|string> // Available starting in MongoDB 4.2.1
}
)
See the Difference? Second parameter should be update not projection because Update one
returns
matchedCount containing the number of matched documents
modifiedCount containing the number of modified documents
upsertedId containing the _id for the upserted document.
A boolean acknowledged as true if the operation ran with write concern or false if write concern was disabled.
So Your code should be
House.updateOne(
{ "expensesHouse._id": id },
{ $set: { "expensesHouse.expenseType": req.body.expenseType } }
).then(house => {
req.flash("success_msg", "Expenses Updated");
res.redirect("/houses/dashboard");
});
});
House.findOneAndUpdate({userId : req.params.userId},
{ $set: { "expensesHouse.$[element].status": req.body.status } },
{ multi:true, arrayFilters: [{ "element.userID" : req.params.subUserId }], new:true })
Your Api reuquest consist of both the IDs (outer as well as inner) like /api/update/:userId/:subUserId