How to get another colletion data with mongoose populate - node.js

I have the following models in node js and i want to get data from file schema and from client schema in just one call, i was reading about populate but have no ideia how to use that.
This is my model
const mongoose = require('mongoose');
const fileSchema = mongoose.Schema({
_id: mongoose.SchemaTypes.ObjectId,
client_id: mongoose.SchemaTypes.ObjectId,
user_id: mongoose.SchemaTypes.ObjectId,
status: String,
name: String,
path: String,
clients: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Client' }]
});
const clientSchema = mongoose.Schema({
_id: mongoose.SchemaTypes.ObjectId,
name: String,
img: String
});
module.exports =
mongoose.model('File', fileSchema, 'files'),
Client = mongoose.model('Client', clientSchema, 'clientes');
This is how i am getting the file data now
exports.getFiles = (req, res, next) => {
File.find({ field: res.locals.field })
.select('_id client_id user_id status name path')
.exec()
.then(file => {
res.status(200).json({
response: file
});
})
.catch(err => {
console.log(err);
res.status('500').json({
error: err
});
});
};
this returns an json response, when i tried to use populate i got an empty array.

You're almost there but you have an issue with your find search. At least with the File model you posted, you don't have a field called 'field' so you won't get any results.
Let's pretend that you're trying to find a file based off of its name and the request is being sent to the url 'blah/files/:name' and it looks like you're using Express.js so this should work.
To use populate, you usually do something like:
File.find({ name: req.params.name })
.populate('clients')
.exec()
.then(files => {
res.status(200).json({
response: files
});
})
.catch(err => {
console.log(err);
res.status('500').json({
error: err
});
});
What you have in your 'select' bit it not necessary since you're starting the search based on the File model and you're just asking it to return all of the fields you have anyway on that model. You get those returned in the result 'for free'.
The populate is flagged out on the 'clients' field since you specified in the File model that it's an object id that references the Client model. Mongoose should handle it basically automagically. However, be careful, ALL of the fields on the Client model will be populated in the clients array of the File. If you want to return only one or a couple fields for your clients, it's there that you should use the select.
Also a note: the find method will return an array even if it's just a result of one document. If you are expecting or wanting just one result, use the findOne method instead.
Update
It looks like there's also a bugaboo in your module exports in the model file, which could be why you are having problems. My coding style is different from yours but here's how I would do it just to be sure that there are no mess ups :
const File = mongoose.model('File', fileSchema);
const Client = mongoose.model('Client', clientSchema);
module.exports = { File, Client };
Then in your router code, you import them as so:
const { File, Client } = require('<path-to-model-file>');

Related

Updating a Subdocument array INSIDE another Subdocument array Mongoose

I am at my wits end with something that is seemingly straightforward:
I need to be able to push new gifts into the Events Array under the specific user. Because each event will have numerous gifts added, I want to keep them all under the user, as they are the one creating the event, and the gifts will live inside of their event where they belong.
The PROBLEM is: when I use the mongoose method 'findByIdAndUpdate', I can only find the main user, and from there, push an event to the events array. What I NEED to be able to do: push gifts to a specific event under that user. I am using mongoose Subdocuments. See my schema below and how I have a subdocument schema (EventSchema) inside of the main user schema, and a subdocument (gift) schema inside the event schema.
SCHEMA:
const Schema = mongoose.Schema;
let giftArr = new Schema({
giftname: String,
giftlink: String,
claimed: Boolean,
claimee: String
})
let eventSchema = new Schema({
eventname: String,
eventowner: String,
date: {
type: Date,
default: Date.now
},
attendees: [
{
attendeename: String
}
],
gift: [giftArr]
})
let userSchema = new Schema({
username: String,
email: { type: String, required: false },
events: [eventSchema]
});
Here are my controllers for my POST & GET routes:
export const insertEventsById = ((req, res) => {
const update = { $push: { events: req.body } }
const id = req.params.userID
Gift.findByIdAndUpdate(id, update, (err, data) => {
if (err) {
console.log(err);
} else {
res.json(data)
console.log(data);
}
})
})
export const getUserById = (req, res) => {
Gift.findById(req.params.userID, (err, user) => {
if(err){
res.send(err)
}
res.json(user)
})
}
To further illustrate, here is my postman GET request for a USER. I can push to the 'events' array (red arrow) as my findByIdAndUpdate method shows above, but when I attempt to go one nested level deeper, into the gift array (green arrow), I cannot find any documentation on that.
I been up and down the mongoose subdocuments and queries pages, and I cannot find a method that will pull specifically the '_id' of the particular event I need. I have even tried the methods on the embedded schemas to specifically look for _id's that way.
Can someone point out where I am going wrong here? Thanks in advance...as always fellow Stacks.

Mongoose append additional properties to the results based on conditional checks

i want to add additional properties to the result document of a mongoose query. i have a Post Model, inside the post model i have added favourites which contains reference to the users who favourited the post, i want to get whether the user has favourited the post and the total number of favourites the post has
Post Model
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const mongoosePaginate = require('mongoose-paginate-v2');
var aggregatePaginate = require('mongoose-aggregate-paginate-v2');
const postSchema = Schema({
title: String,
favourites: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
}],
description: String
});
var Post = mongoose.model('Post', postSchema.plugin(mongoosePaginate));
Post.prototype.hasLiked = function (uid) {
return this.favourites.indexOf(uid) > -1
}
Post.prototype.totalLikes = function () {
return this.favourites.length;
}
module.exports = Post;
Controller
Post.paginate(query,
options,
function (err, result) {
if (err) {
console.log(err)
res.json({
error: err,
status: 501,
message: "Unable to get data"
});
} else {
let isFavourite = result.hasLiked(res.locals.user.uid)
let favouriteLength = result.totalLikes()
console.log(isFavourite)
console.log(favouriteLength)
res.json({
status: 200,
data: result
});
}
}
);
});
Im facing the following error while running the above code
TypeError: result.hasLiked is not a function
Is this an efficient solution, if not please suggest any alternate solution for this scenario.
Post.paginate doesn't return a promise fulfilled with an instance of Post.
Following the documentation ( https://www.npmjs.com/package/mongoose-paginate-v2 ), you will receive your post in result.docs. Loop on it and you can use your getters.

Saving data to array in mongoose

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);

Need a better way to fill values from req.body into a model

I am creating a webapp using the following stack:
Node
Express
MongoDB
Mongoose
I have structured the app into a MVC structure. In the app I need to get create (post) and update (put) data values which I get from res.body and copy them to Mongoose Model. For example I am doing the following:
Mongoose Model:
let mongoose = require('mongoose');
let customerPaymentType = mongoose.Schema({
type: { type: String, required: true, unique: true}
},
{
timestamps: true
}
);
module.exports = mongoose.model('CustomerPaymentType', customerPaymentType);
Controller (Only a part):
let mongoose = require('mongoose');
let CustomerPaymentType = mongoose.model('CustomerPaymentType');
class CustomerPaymentTypeController {
constructor(){}
create(req, res){
let customerPaymentType = new CustomerPaymentType();
this._setCustomerPaymentType(req.body, customerPaymentType);
customerPaymentType.save(error=>{
if (error) res.send(error);
res.json({
message: 'Customer payment type successfully created',
customerPaymentType:{_id: customerPaymentType._id}
});
});
}
//private methods
_setCustomerPaymentType(rawCustomerPaymentType, customerPaymentType){
if (typeof rawCustomerPaymentType.type !== 'undefined') customerPaymentType.type = rawCustomerPaymentType.type.trim();
}
}
module.exports = CustomerPaymentTypeController;
In this model there is only one field, thus populating the model with the data from the req.body in the controller file is easy. But I have other models with more than 30 fields, and it is taking long time to populate them. Is there any easier way to deal with repopulating a model, similar to one present in Ruby on Rails?

How to convert MongooseMap to JSON object to send it from Node js to React?

In mongoose, there is Map data type that allows to store arbitrary keys.
I understand that to get and set values I should use get and set methods. However, when I send an object to frontend, Nodejs sends just empty JSON object.
Is there a way to automatically convert Mongoose object, that has Map type, into JSON object to send over the network, without extracting every key with get on the backend?
My model:
var mongoose = require('mongoose');
var User = require('./user');
var Post = require('./post');
const Schema = mongoose.Schema;
const ObjectId = mongoose.Schema.Types.ObjectId;
const DescriptionSchema = new Schema({
timeStamp: {type: Date, default: Date.now},
postid: {type: ObjectId, ref: 'Post'},
userid: {type: ObjectId, ref: 'User'},
dstrings:{
type: Map,
of: String// key value
}
});
module.exports = mongoose.model('Description', DescriptionSchema);
My controller:
// '/v1/description/add'
api.post('/add', authenticate,(req, res) => {
let description = new Description({
postid: ObjectId(req.body.postid),
userid: ObjectId(req.user.id),
dstrings:req.body.dstrings,
});
description.save(function(err, description) {
if (err) {
res.status(500).json({ message: err });
} else {
// description.dstrings is equal to {} on the frontend
res.status(200).json( description );
}
});
});
JSON.stringify didn't work; I checked database, it has the value.
There is nothing wrong with the .json() Syntax But with arguments of the .save() callback function
The save function's callback will accept the following arguments :
The error
The document that was saved
Please read the mongoose Prototype.save() docs
The link is here
Found the answer here.
The problem was in serialization. To serialize an object that contains Map we can pass a function as the second argument to JSON.stringify as described in the link above. Then we can deserialize on frontend by passing another function to JSON.parse.

Resources