I am trying to return the first and last name of the user who created a document, as well as the one who last modified it. I am using populate()
to do the JOIN between the two collections but it has not worked for me.
This is the area model
var mongo = require('mongoose'),
validator = require('mongoose-unique-validator'),
Schema = mongo.Schema
var model = new Schema({
NAME: { type: String, required: true, unique: true, max: 50, min: 3 },
STATUS: { type: String, default: 'active' },
ADDED_BY: { type: Schema.Types.ObjectId, ref: 'users' },
ADDED_DATE: { type: Date, default: Date.now },
MODIFY_BACKLOG :[{
USER: { type: Schema.Types.ObjectId, ref:'users' },
DATE: { type: Date, default: Date.now },
CURRENT: { type: String },
MODIFIED: { type: String }
}]
}, {collection :'areas'})
model.plugin( validator, { message: 'The {PATH} is not valid or duplicated' } )
module.exports = mongo.model('Area', model )
As you can see the indexes ADDED_BY
and MODIFY_BACKLOG.USER
they are referring to the collection users
.
This is the users model
var mongo = require('mongoose'),
validator = require('mongoose-unique-validator'),
Schema = mongo.Schema
var userSchema = new Schema({
PERSONAL_DATA: {
NAME: { type: String, required: [ true, 'The name is necessary' ], max: 50 },
LAST_NAME: { type: String, required: [ true, 'the lastname is necessary' ], max: 100 },
PHOTO: { type: String, max: 100 },
BIRTHDAY: { type: Date },
MARITIAL_STATUS: { type: Schema.Types.ObjectId, ref: 'maritial_statuses' },
GENDER: { type: String, max: 1 },
EMAIL: { type: String, required: true },
},
COMPANY_DATA: {
JOB: { type: Schema.Types.ObjectId, ref: 'jobs' },
AREA: { type: Schema.Types.ObjectId, ref: 'areas' },
ROLE: { type: Schema.Types.ObjectId, ref: 'roles' },
BOSS: { type: Schema.Types.ObjectId, ref: 'users' },
}
}, { collection: 'users' } )
model.plugin( validator, { message: 'The {PATH} is not valid or duplicated' } )
module.exports = mongo.model('User', userSchema )
As the information is very robust in this model, I decided to separate it into PERSONAL_DATA, COMPANY_DATA
and there are two other indices PAYMENT_DATA, ENGINE_DATA
, each of these keeps indices that refer to the name of the father, but those do not interest me because they have nothing to do with the question and it is a model of approx. . 300 lines.
Inside COMPANY_DATA.AREA
is where the area where the user belongs is included, I don't think it's relevant to this problem, but just in case.
This is the path consumed by the area model
var express = require('express'),
Areas = require('../../models/catalogs/areas'),
app = express()
app.get('/:from', (req, res) => {
var from = parseInt( req.params.from )
var populate = {
path: 'users',
select: 'NAME LAST_NAME'
}
Areas.find()
.sort('NAME').populate( populate )
.limit(10).skip(from)
.exec((error, data) => {
if (error) {
return res.status(500).json({
success: false,
error
})
}
Areas.count({}, ( err, total ) => {
res.status(200).json({
success: true,
data,
total
})
})
})
})
module.exports = app
populate()
I am passing a variable with the same name to the method , where I am making the reference to the collection and what data I need from the user. This is where I am stuck because it is not bringing me the information of the user, it only brings me the information of the area and here I have three questions
How can I make the JOIN of the collections respect me?
How can I tell it that the indexes I need are not explicitly at the top level of the document, but inside PERSONAL_DATA? i tried with
var populate = {
path: 'users',
select: 'PERSONAL_DATA.NAME PERSONAL_DATA.LAST_NAME'
}
But it's still the same, finally
The index MODIFY_BACKLOG
in the area model also has the ObjectId of the user who made the modification, how can I say that the JOIN to users is also done to that index?
I leave an example of the answer
{
"success": true,
"data": [{
"STATUS": "active",
"_id": "5c547f4adadf433914f72c8c",
"NAME": "Contabilidad y Finanzas",
"ADDED_BY": "5c4f562deec6f4defeea759b",
"ADDED_DATE": "2019-02-01T17:18:02.680Z",
"__v": 0,
"MODIFY_BACKLOG": []
}, {
"STATUS": "active",
"_id": "5c645f476491cf093c51c38e",
"NAME": "Recursos Humanos",
"ADDED_BY": "5c4f562deec6f4defeea759b",
"ADDED_DATE": "2019-02-13T18:17:43.036Z",
"__v": 0,
"MODIFY_BACKLOG": [{
"DATE": "2019-02-13T19:43:18.273Z",
"_id": "5c6473561370462754939107",
"USER": "5c4f562deec6f4defeea759b",
"CURRENT": "active",
"MODIFIED": "{\"STATUS\":\"inactive\"}"
}, {
"DATE": "2019-02-13T19:43:24.213Z",
"_id": "5c64735c1370462754939108",
"USER": "5c4f562deec6f4defeea759b",
"CURRENT": "inactive",
"MODIFIED": "{\"STATUS\":\"active\"}"
}]
}],
"total": 5
}
You have some errors that I will detail little by little
ISSUE
You want to query a Mongo Database, from an Express application, using Mongoose as ODM.
The document being consulted belongs to the collection
areas
and has a field that refers toObjectId
another document. Namely, the fieldADDED_BY
contains a reference to the field_id
of a document in the collectionusers
.The query that is performed uses the
populate()
(Mongoose) method to tell the Mongo engine that you want to return the fields set (from the collection documentusers
) in the field that contains the reference.A named object is used
populate
that is passed as an argument to the method.populate()
SOLUTION
Here I will indicate the mistakes you make and also some suggestions:
The first error I see in your code is in the model definition
User
:This line should be:
I guess it's a 'copy and paste' error
Then one suggestion I can give you is how you define your reference:
I normally use a singular name, which also matches the model I'm referring to, not the name of the collection. As I say this is just a suggestion:
Lastly, the way you use the method
populate()
is what is causing your problem.In the official Mongoose documentation , it states the following: (own translation)
Given this, the problem is that you do not specify the path to which you want to replace the data correctly. It seems to me that you are wrongly assuming that it
ref
is the name to use in the path you want to replace.So your object
populate
should look like this:You can see what I use
PERSONAL_DATA.NAME
andPERSONAL_DATA.LAST_NAME
as parameters of theselect
.As for the
array
one that stores the modification log. The solution is the same, but with a small difference. Let's create an object namedpopulateLog
as follows:As you can see, the field
path
of the object isMODIFY_BACKLOG.USER
, and it is precisely the fieldUSER
ofMODIFY_BACKLOG
is the one that we want to fill with the additional data.Mongoose will fill each element of
array
the with the data that we are requesting from it.Now we can call the method in our method chain:
As you can see, I call the method twice
populate()
and pass the corresponding object to each one.Making these small changes your code should work.
An example of the response using
GET
with Postman to my own implementation is the following:As you can see, the returned document contains the information that we have filled using the method
populate()
, both for the fieldADDED_BY
and for the fieldMODIFY_BACKLOG
, since the latter is aarray
, each element ofarray
the contains the correct information.I hope this helps you solve the problem.