Populate multiple fields using lookup in mongodb - mongodb

I am new to mongodb and wanted to populate two ids using lookup
Eg:
{
"sampleId1": "5kjksds8nkjfhsjfi8kl",
"sampleId2": "7jhjshfi9jsfkjsdfkkk"
}
I am using aggregate framework to query the data and wanted to popualte both ids.
I want $loopup to populate both ids which is similar to
Model.find().populate('sampleId1').populate('sampleId2')

For your case, I want to suggest you mongoose-autopopulate like this
const autopopulate = require('mongoose-autopopulate')'
const sampleSchema = new Schema({
sampleId1: {type: Schema.ObjectId, ref: 'ColleactionName', autopopulate: {select: 'firstName, lastName'}},
sampleId2: {type: Schema.ObjectId, ref: 'ColleactionName', autopopulate: {select: 'firstName, lastName'}}
})
sampleSchema.plugin(autopopulate)
module.exports = mongoose.model('sampleSchema', sampleSchema)
now whenever you request for find it automatically populates all field
who have Schema.ObjectId
let criteria = {},
projection = {},
options = {lean: true}
Model.find(criteria, projection, options, (err, result) => {
console.log(result); // See out-put
})
The second thing you need to check in your schema that sampleId1 and sampleId2 both have type type: Schema.ObjectId with reference of collection name ref: 'ColleactionName'
the second way to this thing which you already have done you question
sampleSchema.
find(...).
populate('sampleId1').
populate('sampleId2').
exec();

Related

Can database references in mongodb reference two databases?

I am using MongoDB via mongooose. Can database references reference two databases at the same time?
field_name: {
type: mongoose.Schema.Types.ObjectId,
ref: 'collectionA',// can I reference collectionA and collectionB too?
required: true,
},
See code above.
The field, field_name, can be an objectId from collectionA or collectionB. How can I reflect that in my mongoose schema?
I guess you are looking for mongoose dynamic reference via refPath.
const someSchema = new Schema({
field_name: {
type: Schema.Types.ObjectId,
required: true,
refPath: 'onCollection'
},
onCollection: {
type: String,
required: true,
enum: ['collectionA', 'collectionB']
}
});
In this case, Instead of a hardcoded model name in ref, refPath means Mongoose
will look at the onCollection property to find the right model.
For example if we have this document:
{
field_name: some_id,
onCollection: 'collectionA'
}
Collection.find().populate('field_name') will populate the field from collectionA. And if the onCollection field was valued with collectionB, it would have populated it from collectionB.
This scenario only works if you want to reference one collection at a time, but the collection is dynamic.
If you need to reference both collections at the same time, there is no mongoose schema design to support array of references as far as I know.
You can just ignore ref in your schema, and pass in the value of ref when you want to populate:
populate({
path: 'field_name',
model: 'collectionA'
})
Then you can have multiple populates. Same applies for $lookup.

how to multi ref in mongoose

I am trying to ref two documents in one property, i have been checking the oficial documentation but i didn't get the solution...
At the moment i am trying this...
items: [{
type: mongoose.Schema.Types.ObjectId,
ref: ['items','users']
}],
In the documentation they mention refPath... but i could not populate both models... any solution for this?
// LINK TO DOCUMENTATION
https://mongoosejs.com/docs/populate.html#dynamic-ref
You don't need to pass refs in arrays. Here is the simple solution:
Mongoose Model (Report.js):
You can clearly see that I did not pass any ref to my Model but still, you can use multiple refs in post/get APIs. I will show you next.
const mongoose = require('mongoose');
const reportSchema = new mongoose.Schema({
reportFrom : {
type: mongoose.Schema.Types.ObjectId,
require: true,
},
reportTo: {
type: mongoose.Schema.Types.ObjectId,
require: true,
},
}
);
module.exports = mongoose.model("report", reportSchema);
Above "reportTo" means the Id of someone post whom the user is going to report or the id of user profile whom the user is going to report. Means "reportTo" may be an ID of User Profile or Post. So, if "reportTo" contains user Id then I have to refer to users collection but if "reportTo" contains post Id then I have to refer to posts collection. So, how I can use two refs. I will simply pass type query from postman to tell which ref to go either posts or users. See below my API request:
APIs file (reports.js)
const reports = req.query.type === "Post" ? await Report.find({reportTo: req.params.id}).populate({
path: 'reportFrom', // attribute name of Model
model: "User", // name of model from where you want to populate
select: "name profilePicture", // get only user name & profilePicture
}).populate({
path: 'reportTo', // attribute name of Model
model: "Post",
}).sort({ _id: -1 })
: req.query.type === "Profile" ? await Report.find({reportTo: req.params.id}).populate({
path: 'reportFrom', // attribute name of Model
model: "User",
select: "name profilePicture",
}).populate({
path: 'reportTo', // attribute name of Model
model: "User",
select: "name profilePicture",
})
.sort({ _id: -1 })
: null
return res.status(200).json(reports);
See the line 7 & 15, you can clearly see how I use two different refs for same attribute. In first case, reportTo is refered to Post Model & in second case reportTo is refered to User Model.

MongoDB Mongoose save object with nested objects

I'm new to MongoDB and I'm creating a simple db with Mongoose with the following models: User, Game and Players.
So, one user contains none or many games. Every game has to players, and each player refers to a user. Like this (I simplified the schemas for clarity):
const UserSchema = new Schema({
name: String,
games: [{
type: Schema.Types.ObjectId,
ref: 'game'
}]
});
const GameSchema = new Schema({
mode: Number,
players: {
type: [{
type: Schema.Types.ObjectId,
ref: 'player'
}],
required: true
}
});
const PlayerSchema = new Schema({
order: Number,
isWinner: Boolean,
user: {
type: Schema.Types.ObjectId,
ref: 'user',
required: true
}
});
So, now in the frontend I want to send a petition to the backend to create a new game for users Joe (_id:11111) and Bob (_id:22222) and so I send a POST to /api/games with the body { users: [ 11111, 22222 ] }
Now my question is, for the backend to create a new game, it also has to create 2 players. What's the best way to achieve this?
In the Game.create() method, shall I retrieve the data, create and save the players, create the game, assign the players, save the game, and also update the users and add the game ids?
I also read about Mongoose middleware, where you can set certain functions to be executed before or after some operations. So maybe it's better:
pre function before Game.create, to create the players
post function before Game.create, to update the users
This last one seems cleaner.
What's the best way? Maybe another one I have not considered?
Thanks
I would suggest you using the post and pre functions defined in the mongoose middleware. They're pretty straightforward and neat to use. It will probably solve your problem.
Here is a personal example of a problem we had; In our case, we had to assign a userId from a sequence in the database. We used the following code:
var UserSchema = new Schema({
username: { type: String, required: true, unique: true },
id: { type: String },
...
});
UserSchema.pre('save', function(next) {
let doc = this;
let id = 'userSeq'
Sequence.findByIdAndUpdate(id, { $inc : {nextSId : 1} }, function(error,data) {
if(error)
next(error)
doc.id = data.nextSId-1;
next();
})
});
My suggestion is that before you create the game, you can search for the users and add a reference to the game. If I were you, I would use the findAndModify query of mongodb to find the users or create if they do not exist yet.

mongoose - sort by array length

I am having this schema
var PostSchema = new Schema({
title: {type: String, trim: true}
, description: {type: String, trim: true}
, votes: [{ type: Schema.ObjectId, ref: 'User' }]
})
I want to sort posts based on votes i.e, I need to sort by array length.
Tried the usual way, but din't work
PostSchema.statics.trending = function (cb) {
return this.find().sort('votes', -1).limit(5).exec(cb)
}
Any help?
version of mongoose I am using is 2.7.2
You can't do that directly. To be able to sort on array length, you have to maintain it in a separate field (votes_count, or whatever) and update it when you push/pull elements to/from votes.
Then you sort on that votes_count field. If you also index it, queries will be faster.

Mongoose: Populate a populated field

I'm using MongoDB as a log keeper for my app to then sync mobile clients. I have this models set up in NodeJS:
var UserArticle = new Schema({
date: { type: Number, default: Math.round((new Date()).getTime() / 1000) }, //Timestamp!
user: [{type: Schema.ObjectId, ref: "User"}],
article: [{type: Schema.ObjectId, ref: "Article"}],
place: Number,
read: Number,
starred: Number,
source: String
});
mongoose.model("UserArticle",UserArticle);
var Log = new Schema({
user: [{type: Schema.ObjectId, ref: "User"}],
action: Number, // O => Insert, 1 => Update, 2 => Delete
uarticle: [{type: Schema.ObjectId, ref: "UserArticle"}],
timestamp: { type: Number, default: Math.round((new Date()).getTime() / 1000) }
});
mongoose.model("Log",Log);
When I want to retrive the log I use the follwing code:
var log = mongoose.model('Log');
log
.where("user", req.session.user)
.desc("timestamp")
.populate("uarticle")
.populate("uarticle.article")
.run(function (err, articles) {
if (err) {
console.log(err);
res.send(500);
return;
}
res.json(articles);
As you can see, I want mongoose to populate the "uarticle" field from the Log collection and, then, I want to populate the "article" field of the UserArticle ("uarticle").
But, using this code, Mongoose only populates "uarticle" using the UserArticle Model, but not the article field inside of uarticle.
Is it possible to accomplish it using Mongoose and populate() or I should do something else?
Thank you,
From what I've checked in the documentation and from what I hear from you, this cannot be achieved, but you can populate the "uarticle.article" documents yourself in the callback function.
However I want to point out another aspect which I consider more important. You have documents in collection A which reference collection B, and in collection B's documents you have another reference to documents in collection C.
You are either doing this wrong (I'm referring to the database structure), or you should be using a relational database such as MySQL here. MongoDB's power relies in the fact you can embed more information in documents, thus having to make lesser queries (having your data in a single collection). While referencing something is ok, having a reference and then another reference doesn't seem like you're taking the full advantage of MongoDB here.
Perhaps you would like to share your situation and the database structure so we could help you out more.
You can use the mongoose-deep-populate plugin to do this. Usage:
User.find({}, function (err, users) {
User.deepPopulate(users, 'uarticle.article', function (err, users) {
// now each user document includes uarticle and each uarticle includes article
})
})
Disclaimer: I'm the author of the plugin.
I faced the same problem,but after hours of efforts i find the solution.It can be without using any external plugin:)
applicantListToExport: function (query, callback) {
this
.find(query).select({'advtId': 0})
.populate({
path: 'influId',
model: 'influencer',
select: { '_id': 1,'user':1},
populate: {
path: 'userid',
model: 'User'
}
})
.populate('campaignId',{'campaignTitle':1})
.exec(callback);
}
Mongoose v5.5.5 seems to allow populate on a populated document.
You can even provide an array of multiple fields to populate on the populated document
var batch = await mstsBatchModel.findOne({_id: req.body.batchId})
.populate({path: 'loggedInUser', select: 'fname lname', model: 'userModel'})
.populate({path: 'invoiceIdArray', model: 'invoiceModel',
populate: [
{path: 'updatedBy', select: 'fname lname', model: 'userModel'},
{path: 'createdBy', select: 'fname lname', model: 'userModel'},
{path: 'aircraftId', select: 'tailNum', model: 'aircraftModel'}
]});
how about something like:
populate_deep = function(type, instance, complete, seen)
{
if (!seen)
seen = {};
if (seen[instance._id])
{
complete();
return;
}
seen[instance._id] = true;
// use meta util to get all "references" from the schema
var refs = meta.get_references(meta.schema(type));
if (!refs)
{
complete();
return;
}
var opts = [];
for (var i=0; i<refs.length; i++)
opts.push({path: refs[i].name, model: refs[i].ref});
mongoose.model(type).populate(instance, opts, function(err,o){
utils.forEach(refs, function (ref, next) {
if (ref.is_array)
utils.forEach(o[ref.name], function (v, lnext) {
populate_deep(ref.ref_type, v, lnext, seen);
}, next);
else
populate_deep(ref.ref_type, o[ref.name], next, seen);
}, complete);
});
}
meta utils is rough... want the src?
or you can simply pass an obj to the populate as:
const myFilterObj = {};
const populateObj = {
path: "parentFileds",
populate: {
path: "childFileds",
select: "childFiledsToSelect"
},
select: "parentFiledsToSelect"
};
Model.find(myFilterObj)
.populate(populateObj).exec((err, data) => console.log(data) );