Can't populate when referencing a SubDocument in mongoose - node.js

I have a database with three relevant models, here are the mongoose schemas
// models/company.js
let companySchema = new Schema({
rut : String,
name : String,
owner : { type: Schema.Types.ObjectId, ref : 'User' }
})
export default mongoose.model('Company', companySchema)
// models/account.js
let accountSchema = new Schema({
code : String,
name : String,
type : {
id : Number,
name : String
}
})
// I have two different exports, because sometimes I just need the schema,
// and others I need the model
export default accountSchema
export const Account = mongoose.model('Account', accountSchema)
// models/mayor.js
let mayorSchema = new Schema({
account : { type: Schema.Types.ObjectId, ref : 'Account' },
company : { type: Schema.Types.ObjectId, ref : 'Company' },
month : Number,
year : Number,
data : [{
date : Date,
detail : String
}]
})
export default mongoose.model('Mayor', mayorSchema)
I want to query my Mayor model from mayorSchema, my query is like this
import {Account} from '../models/account'
import Company from '../models/company'
import Mayor from '../models/mayor'
Mayor
.find({ 'company' : companyId, 'month' : month, 'year' : year })
.populate('company')
.populate('account')
.exec((err, mayores) => {
if (err) {
console.log(JSON.stringify(err))
res.status(500)
res.json({
code: "001",
message: "Some error with the server. Please try later"
})
} else {
res.status(200)
res.json(mayores)
}
})
The result of that query is an array, but every object in the array has the account property set to null. I don't know why is that happening, if I query the data directly on mongo console, all is fine, all the documents has correctly set their account property. Some things that might be useful to know:
I'm using express.js, part of the notation is from express.
My account property is correctly populated
The account schema is a subdocument (embedded) in another model, but the (account) model is correctly registered in mongoose.
The documents in mayors collection where previously inserted with Mayor.insertMany method, and after that insert, if I inspect the just created documents, they have the account field correctly set.
So why is this happening? why can't I populate my account field. I guess that it is something related to referencing on sub documents, but I would have expected that a failed population would return just an ObjectId, not null.

Related

Mongoose findOneAndUpdate cast error with custom _id

I have my Person schema like this :
const schema = new mongoose.Schema({
_id: Number,
name: String,
birthday: Date,
sex: String
});
schema.pre('findOneAndUpdate', async function (next) {
try {
let counter = await Counters.findByIdAndUpdate('person',
{
$inc: {
value: 1
}
},
{ new: true}
);
this._update._id = counter.value;
next();
}
catch (err) {
next(err);
}
});
The problem is when I try to add some new persons with findOneAndUpdate and upsert: true, it generates a CastError: Cast to ObjectId failed for value "18" at path "person".
My _id is defined as a Number so I don't understand why it's trying to cast it to an ObjectId ?
Update :
I found my problem, the Person model is referenced in some other model but I forgot to change the ref type in the other model...
person: {
type: Number, //HERE
ref: 'person',
required: true
}
You can change the type of the_id property although ins't a good approach, but actually you can't change the value since it's immutable and represents the primary key of the document. Keep in mind that _id is very important for MongoDB life cycle, like indexing. If you aim to change an Entity key, you can create other property, something like person_id.
_id is an auto generated property for MongoDB. If you want to add try a different name for the Id attribute like "personId" or you can use the auto generated Id by MongoDB without creating a seperate Id.

default value for ref collection in mongoose

I have a user profile, I have a field of 'earning' and it look like this in the schema
earning: {
type: Schema.Types.ObjectId,
ref: 'Earning'
}
This how do I make a default value for earning field when a new user is created? I can't do this
earning: {
type: Schema.Types.ObjectId,
ref: 'Earning',
default: 0
}
I got error of
Cast to ObjectId failed for value "0" at path "earning"
What you are doing wrong here is trying to cast a number on an ID field. Since it's a reference of another object Id field, you can not set 0 to it. What you need to do is to set null when a user is created in db and initialize it with a null value of earning.
Like:
earning: {
type: Schema.Types.ObjectId,
ref: 'Earning',
default: null
}
When instantiating a document based on a Schema which has a key of type 'ObjectId' and a ref to another collection, the only way that I've found to set a 'default' value is through the use of Mongoose middleware at the schema level as described here. For example, setting a comment's author to a default 'guest' document from a User collection when the author is not logged in might look like this:
// user document in MongoDB
{
_id: ObjectId('9182470ab9va89'),
name: 'guest'
}
// CommentSchema
const mongoose = require('mongoose')
const CommentSchema = mongoose.Schema({
author: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
},
body: String
})
CommentSchema.pre('save', function (next) {
this.author == null ? this.author = '9182470ab9va89' : null
next()
})
module.exports = mongoose.model('Comment', CommentSchema)
This example uses the 'save' pre hook with the ObjectId hardcoded in the schema for demonstration purposes, but you can replace the hardcoding of the ObjectId with a call to your backend or however else you'd like to get that value in there.
As I understand earning is indication of how much user earn so it should be of type Number not ObjectId
so try to change your Schema to be
earning: {
type: Number,
ref: 'Earning',
default: 0
}
so you can use 0
Note: if you should use ObjectId for some reason so the answer of 'Haroon Khan' is the correct answer.

node js, mongodb populate the populated

I've hit a wall in my server when I needed to get data from my server.
The following represents my schemas:
Schema one:{
name: String
}
Schema two:{
code:String,
name_id: refid: schema One
}
Schema three:{
phone:number
code:[refid: Schema two]
}
If I needed data from schema three, and the objects from object ids that are saved in the code array I would use populate and I would get the object referenced by object id.
Question is is it possible to populate the populated data?
If populate schema three
I would get objects such as:
{phone : 000911,
code: :{code:String,
name_id: refid: schema One}
in the previous example I want to populate the name id, is that possible?
With Mongoose, you can populate your schema with dot notation like this:
const One = new Schema({
name: String
})
const Two = new Schema({
code: String,
name: {
type: Schema.ObjectId,
ref: 'One'
}
})
const Three = new Schema({
phone: number
code: [{
type: Schema.ObjectId,
ref: 'Two'
}]
})
Three.find((err, three) => {
if (err) console.log(err)
console.log(three)
// => {
// phone : "the phone number from schema Three",
// code: {
// code: "the code from schema Two",
// name: "the name from schema One"
// }
// }
})
.populate('code.name')

Updating a Record in Mongo After Retrieving Its ID From Another Record

I am trying to make an API point that would do the following. I submit an Object ID in the path. The record with that ID is found. Then, the program looks into a certain field of this object. The field contains an ObjectID for another entry in the database. At last, I need to pull up that record and increment a certain field in it.
In short, I have a child->parent relationship between certain records and would like the ability of incrementing a certain field within the parent record by submitting the child's id to the API point.
Here is the code I had that did the basic child increment. How can I go about doing it for the parent?
router.get('/today/parent/up/:id', function(req, res){
var collection = db.get('Activity');
collection.update({
_id: req.params.id
},
{
$inc: {
"repetitions.today": 1,
"repetitions.total": 1
}
}, function(err, activity){
if (err) throw err;
res.json(activity);
});
})
First use mongo references, heres documenttion:
https://docs.mongodb.com/manual/reference/database-references/
here's mongoose documentation
http://mongoosejs.com/docs/2.7.x/docs/populate.html
Basically You need to do this:
var mongoose = require('mongoose')
, Schema = mongoose.Schema
var PersonSchema = new Schema({
name : String
, age : Number
, stories : [{ type: Schema.ObjectId, ref: 'Story' }]
});
var StorySchema = new Schema({
_creator : { type: Schema.ObjectId, ref: 'Person' }
, title : String
, fans : [{ type: Schema.ObjectId, ref: 'Person' }]
});
var Story = mongoose.model('Story', StorySchema);
var Person = mongoose.model('Person', PersonSchema);
Then you could use .populate() method, and then you could extract your populated model and make changes and save them with .save(), but remember to use it in populated model, not the parent one. For ex. You've got author which contains reference to books, so you make request
author.findOne({'name': 'King'}).populate('books').exec((err, king) => {
let book0 = king.books[0];
book0.title = 'I need to change this one';
book0.save((err, data) => {
console.log('saved referenced object')
}
})

Mongoose populate return undefined

I'm currently trying to develop an app using mongo and node.js.
I am facing a problem when I want to build a query who use the populate option.
Here are my Schemas :
// Schema used by mongoose
var userSchema = new mongoose.Schema(
{
_id: mongoose.Schema.Types.ObjectId,
login: String,
password: String,
movies: [ { type: mongoose.Schema.Types.ObjectId, ref: movieModel} ],
admin: Boolean
},{ collection: "user" });
var movieSchema = new mongoose.Schema(
{
_id: mongoose.Schema.Types.ObjectId,
title: String,
}, { collection: "movie" });
As you can see, each user have an array of movies, this array contains valid ids of movies. What I want is to have the movies of an user. This is how I build my query :
var query = userModel.findOne({ login: req.session.user["login"] })
.populate("movies");
query.exec(function(err, user)
{
if (err)
throw err;
console.log(user.movies[0].title);
});
The query is executed successfully, but when I try to display the title of the first movie at the console.log line I got an error "TypeError: Cannot read property 'title' of undefined". I checked the documentation of mongoose and don't understand why I'm getting this error.
I would like to specify that my database contains valid data.
I put mongoose in debug mode, and this is the query that is executed :
Mongoose: user.findOne({ login: 'user' }) { fields: undefined }
Mongoose: user.find({ _id: { '$in': [ ObjectId("52e2a28949ad409834473e71"), ObjectId("52e2a28949ad409834473e79") ] } }) { fields: undefined }
The two ids on the second line are valid ids of movies. I would like to display their name.
Thanks a lot for your help.
What is the value of this: ref: movieModel?
movieModel would need to be set to the string like "Movie". See here for more information. It will need to match the identifier provided when you create the Movie model.
var Movie = mongoose.model('Movie', movieSchema);
So, you might have in a schema:
var userSchema = mongoose.Schema({
name: String,
favorite_movies: { type: Schema.Types.ObjectId, ref: 'Movie' }
});
var User = mongoose.model('User', userSchema);
I've used the string Movie in both the Schema definition and when creating the Movie type. They need to be exactly the same.
MongooseJs uses the string name of the Model to determine where to fetch the documents from when using ref and populate.
In the debug output, you can see how Mongoose is actually querying the wrong collection, as I'd expect it to be using movies.find to find the relevant Movie documents.

Resources