Mongoose update query does not work on nested object - node.js

I have a model with nested object and I want to update single field of that document but unable to do it. I could not understand why it's not working.
This is my model
var sampleItemSchema = new Schema({
id: {
type: String,
required: true
},
content: {
type: Object,
required: true
},
location: Object,
createdAt: {
type: Date,
default: Date.now
},
sample: {type: mongoose.Schema.Types.ObjectId, ref: 'Sample'}
});
And this is my one of document.
{ createdAt: Thu Apr 21 2016 19:46:17 GMT+0200 (CEST),
__v: 0,
sample: 571911df9a97810c3f35d83d,
location: { city: 'Kildare' },
content:
{ images: [],
price: { currency: 'EUR', amount: 2000 },
createdAt: '2016-04-21T17:46:17.349Z',
category: { id: '2012', name: 'Animals | Ponies' },
body: 'Beaulieu Ginger Pop (Ben) is a 14 year old 13.2hh grey roan New Forest pony. He has a full green passport, is microchipped and is fully up...',
title: '13.2hh All rounder Gelding' },
id: '12123191',
_id: 571911e99a97810c3f35d845 }
Here what I tried yet.
I am just giving part of code
models.SampleItem.find({
sample: sample
}, function(err, sampeItemList) {
console.log('Total sample: ', sampeItemList.length);
async.eachSeries(sampeItemList, function(item, next) {
item.content.body = "want to update this field";
item.save(function(err, updatedItem) {
console.log('Updated description...', index);
})
})
})
Am I doing something wrong?

Mongoose does not allow you to use Object as a schema type. Instead set the type to Schema.Types.Mixed. This will give you an object with whatever properties you want to set.
You can see the list of all the valid schema types in the Mongoose Schema Types documentation.
(You probably want to change the location field to Schema.Types.Mixed as well.)

Mongoose doesn't allow 'Object' as a type. Use 'Schema.Types.Mixed' for properties which have flexible/unknown values.

Related

Mongoose findById is not returning all fields

I'm calling findById using mongoose and it's not returning all fields, or at least it's not mapping to a field correctly. But it returns that field if I use aggregate
I have the following schema
const ratingSchema = new mongoose.Schema({
rating: {
type: Number,
default: 0,
min: 0,
max: 5
}
})
const locationSchema = new mongoose.Schema({
name: {
type: String,
required: true,
},
address: {
type: String,
required: true,
},
rating: ratingSchema,
facilities: [String],
});
locationSchema.index({coords: '2dsphere'});
mongoose.model('Location', locationSchema);
When I call
const Loc = mongoose.model('Location');
const locationsReadOne = (req, res) => {
Loc
.findById(req.params.locationid)
.exec((err, location) => {
if (!location) {
return res.status(404).json({"message":"location not found"});
} else if (err) {
return res.status(404).json(err);
}
console.log("locationsReadOne:",location);
res.status(200).json(location);
});
};
It returns the schema, but the rating field is not being returned. Here is a console.log of the returned object:
locationsReadOne: {
facilities: [ 'Hot drinks', 'Food', 'Premium wifi' ],
_id: 5f88bfdc4df4ca7709462865,
name: 'Starcups',
address: '125 High Street, Reading, RG6 1PS'
}
If I call Loc.aggregate, the rating field is returned:
{
_id: 5f8b2ee15b0b6784a847b600,
facilities: [ 'Tea', ' Restroom' ],
name: 'Tea Leaf',
address: '2192 Green St.',
rating: 0,
}
{
_id: 5f88bfdc4df4ca7709462865,
name: 'Starcups',
address: '125 High Street, Reading, RG6 1PS',
rating: 3,
facilities: [ 'Hot drinks', 'Food', 'Premium wifi' ]
}
Any idea why this would happen? I can clearly see the rating field in each of the documents in MongoDB compass and they are all listed as type Double. Why are they being returned in aggregate, but not in findById(id) or even in find()?
Thank you.
When you use find in mongoose, it will populate the document with the fields from the schema. For example - if you remove a field from the schema, you may not see that on the found document, even if it is in the database. Aggregate and Compass are showing you exactly what's in the database, so that data is there, it is just not being populated on the document because it doesn't see it on the schema.
The reason for that is your ratingSchema is an object with a rating property. So mongoose is looking for something like:
{
name: ...
rating: {
rating: 2
}
}
And not finding it, so it's not populating. To fix this, I would not define rating as a subschema, and instead define your schema as
const locationSchema = new mongoose.Schema({
name: {
type: String,
required: true,
},
address: {
type: String,
required: true,
},
rating: {
type: Number,
default: 0,
min: 0,
max: 5
},
facilities: [String],
});

Populate subdocument array field with Mongoose

I know this question has been asked before, but after hours of research I have tried several ways to solve my problem without success.
Schema
const ValuationsSchema = new Schema(
{
value: { type: Number, required: true, min: 0, max: 5 },
author: {
type: Schema.Types.ObjectId,
refPath: "fromModel",
required: true,
},
fromModel: {
type: String,
required: true,
enum: ["usertype1", "usertype2", "usertype3"],
trim: true,
},
},
{
timestamps: true,
}
);
const UserSchema = new Schema(
{
name: { type: String, required: true, trim: true },
valuations: [ValuationsSchema],
},
{
timestamps: true,
}
);
What am I trying to do
I am trying to populate the author field of ValuationsSchema, in the valuations array of a user.
Sample data
Result expected
{
_id: 5f1ef9f6039fea10c437939c,
name: 'Jose Maria',
createdAt: 2020-07-27T15:59:50.529Z,
updatedAt: 2020-08-01T15:34:47.414Z,
valuations: [
{
_id: 5f258b973c0ac544869c0434,
value: 5,
author: Object,
fromModel: 'particular',
createdAt: 2020-08-01T15:34:47.414Z,
updatedAt: 2020-08-01T15:34:47.414Z
}
]
}
Result gotten
{
_id: 5f1ef9f6039fea10c437939c,
name: 'Jose Maria',
createdAt: 2020-07-27T15:59:50.529Z,
updatedAt: 2020-08-01T15:34:47.414Z,
valuations: [
{
_id: 5f258b973c0ac544869c0434,
value: 5,
author: 5f1edaa83ce7cf44a2bd8a9a,
fromModel: 'particular',
createdAt: 2020-08-01T15:34:47.414Z,
updatedAt: 2020-08-01T15:34:47.414Z
}
]
}
Already tried solutions
At the moment I manually populated the field, but as it is a good practice, I am trying to use the API when possible.
As far as I am concerned executing this should get the job done, but it doesn't.
await user.populate("valuations.author").execPopulate();
Also tried this without luck:
await user.populate({
path: "valuations",
populate: {
path: "author",
},
}).execPopulate();
Tried the deepPopulate package too, but yet same result.
The refPath should be from parent level valuations.fromModel,
Change it in your schema,
author: {
type: Schema.Types.ObjectId,
refPath: "valuations.fromModel",
required: true,
}
Single document wise population,
let user = await User.find();
let user1 = await user[0].populate("valuations.author").execPopulate();
All documents population
let users = await User.find().populate("valuations.author").execPopulate();
Note: There is a bug #6913 in mongoose version 5.2.9, refPath not working in nested arrays, Make sure you have installed latest version.

Getting CoreMongooseArray instead of normal array

I have this schema:
var orderSchema = new mongoose.Schema({
history: [{
"type": {
type: String,
enum: [
'ORDER_HISTORY_DRIVER_DETAILS',
'ORDER_HISTORY_LOADING',
'ORDER_HISTORY_LOCATION',
'ORDER_HISTORY_UNLOADING'
],
required: true
},
date: {
type: Date
},
state: {
type: String,
enum: [
'ORDER_HISTORY_STEP_STATE_COMPLETED',
'ORDER_HISTORY_STEP_STATE_CURRENT',
'ORDER_HISTORY_STEP_STATE_FUTURE',
],
default: 'ORDER_HISTORY_STEP_STATE_FUTURE',
required: true
}
}]
})
At one point, I need to remove all subdocuments that have a type of "ORDER_HISTORY_LOCATION", so I'm running this:
let result = await Order.findOneAndUpdate(
{orderId: req.params.orderId},
{
$pull: {
history: {type: "ORDER_HISTORY_LOCATION"}
}
}, {new: true}
);
When i log "result.history" i get this:
CoreMongooseArray [
{ state: 'ORDER_HISTORY_STEP_STATE_CURRENT',
_id: 5caf8a41641e6717d835483d,
type: 'ORDER_HISTORY_DRIVER_DETAILS' },
{ state: 'ORDER_HISTORY_STEP_STATE_FUTURE',
_id: 5caf8a41641e6717d835483c,
type: 'ORDER_HISTORY_LOADING',
date: 2019-05-08T09:00:00.000Z },
{ state: 'ORDER_HISTORY_STEP_STATE_FUTURE',
_id: 5caf8a41641e6717d835483b,
type: 'ORDER_HISTORY_LOADING',
date: 2019-05-09T09:00:00.000Z },
{ state: 'ORDER_HISTORY_STEP_STATE_FUTURE',
_id: 5caf8a41641e6717d8354837,
type: 'ORDER_HISTORY_UNLOADING',
date: 2019-05-13T09:00:00.000Z } ]
What is this "CoreMongooseArray"? I can't do anything with it. I also can't find any documentation on it.
CoreMongooseArray seems to be inheriting the Array type and has almost the same behavior.
Source code (at the time of writting) : https://github.com/Automattic/mongoose/blob/3e523631daa48a910b5335c747b3e5d080966e6d/lib/types/core_array.js
In case you want to convert it to a simple array, just do this :
const history = Array.from(...result.history)
Beware, if this array contains objects, each object will have undesirable additional Mongoose properties, as they are Mongoose schemas documents. You will need to convert them into plain JavaScript objects :
const history = Array.from(...result.history).map(v => v.toJSON())
Hope it helps.
This worked for me!
const history = Array.from([...result.history])

Why is isObject (with default settings) returning empty objects?

From the documentation for toObject it states that the minimize option will remove empty objects (defaults to true)
and also the documentation for toJSON states this method accepts the same options as Document#toObject.
However I have noticed two instances where this doesn't appear to be true (I haven't done an exhaustive check yet).
My main question is: Am I missing something (this is the intended output) or is this a bug?
Using version 3.8.7.
Given the simple schemas:
var CommentSchema = mongoose.Schema({
body: String,
created: {
by: {
type: String,
required: true
},
date: {
type: Date,
default: Date.now
}
}
});
var BlogSchema = mongoose.Schema({
title: String,
blog: String,
created: {
by: {
type: String,
required: true
},
date: {
type: Date,
default: Date.now
}
},
comments: [CommentSchema]
});
var Blog = mongoose.model('Blog', BlogSchema);
Case 1
Blog.findOne({
_id: id
}, function(err, blog) {
console.log(blog); // causes toJSON to be executed
});
// outputs:
{
title: 'My first blog! #Super',
blog: 'This is my very first #blog! I hope you enjoy it. #WOOHOO',
_id: 532cb63e25e4ad524ba17102,
__v: 0,
comments: [], // SHOULD THIS BE INCLUDED??
created: {
by: 'Joe',
date: Fri Mar 21 2014 17: 59: 26 GMT - 0400(EDT)
}
}
Case 2
Blog.findOne({
_id: id
}, 'title', function(err, blog) {
console.log(blog); // causes toJSON to be executed
});
// outputs:
{
title: 'My first blog! #Super',
_id: 532caa3841176afb4a7c8476,
created: {} // SHOULD THIS BE INCLUDED??
}
First, my understanding for forcing toJSON by using console.log was incorrect. To do so use: console.log(JSON.parse(JSON.stringify(blog)));
Using the correct method to show the JSON representation of the mongoose document solves case #2:
Blog.findOne({
_id: id
}, 'title', function(err, blog) {
console.log(JSON.parse(JSON.stringify(blog))); // causes toJSON to be executed
});
// outputs:
{
title: 'My first blog! #Super',
_id: 532caa3841176afb4a7c8476
}
However, the empty array from case #1 is still returned:
Blog.findOne({
_id: id
}, function(err, blog) {
console.log(JSON.parse(JSON.stringify(blog))); // causes toJSON to be executed
});
// outputs:
{
title: 'My first blog! #Super',
blog: 'This is my very first #blog! I hope you enjoy it. #WOOHOO',
_id: 532cb63e25e4ad524ba17102,
__v: 0,
comments: [], // SHOULD THIS BE INCLUDED??
created: {
by: 'Joe',
date: Fri Mar 21 2014 17: 59: 26 GMT - 0400(EDT)
}
}
So I guess the answer is if the documentation means only empty objects (and not arrays) than this is expected behavior.

Unable to update embedded item in a mongo document (no method update)

I have a mongoose schema that looks like this, that I am able to update and query just fine:
// Subscriber Schema
var Subscriber = new Schema({
'user': ObjectId,
'level': { type: String, enum: [ 'sub', 'poster', 'blocked' ] }, //blocked means users has opted out of list
'dateAdded': { type: Date, default: Date.now }
});
// Group Schema
var Group = new Schema({
'groupEmail': { type: String, lowercase: true, validate: [validatePresenceOf, 'an email is required'], index: { unique: true } },
'groupOwner': ObjectId,
'groupName': String,
'twitterFeed': String,
'subscribers': [Subscriber],
'createdAt': { type: Date, default: Date.now }
});
I'm trying to update a Subscriber within the Group document, and change its level, then save the change. I'm able to find the group just fine using:
Group.findOne({ _id: req.params.groupId }, function(err, g)
Once I have the group, I want to find a subscriber so I can update it. If I try:
g.subscribers.update({ 'user': req.params.userId }, { level: newStatus });
I get:
TypeError: Object { user: 4fc53a71163006ed0f000002,
level: 'sub',
_id: 4fd8fa225904a5451c000006,
dateAdded: Wed, 13 Jun 2012 20:37:54 GMT },{ user: 4fda25ac00cd9bdc4f000004,
level: 'sub',
_id: 4fda270bbce9f8d058000005,
dateAdded: Thu, 14 Jun 2012 18:01:47 GMT },{ user: 4fda2a634499dfd16e00000d,
level: 'sub',
_id: 4fda2a634499dfd16e00000e,
dateAdded: Thu, 14 Jun 2012 18:16:03 GMT } has no method 'update'
I've tried various permutations of update, find, etc. but always get the "has no method" error. What am I doing wrong? Is there a different way I should be updating Subscriber.level?
Thanks!!
#eltoro is mostly correct, arrays do not have an update method, and the suggestion to use Group.update is a good one, but the update syntax is missing the $ in subscribers.$.level.
Group.update(
{_id: req.params.groupId, 'subscribers.user': req.params.userId},
{ 'subscribers.$.level': newStatus }, function(err, result) {}
);
I think you're getting the error because you're calling update on the instance instead of the model.
Maybe try something like this:
Group.update(
{_id: req.params.groupId, subscribers.user: req.params.userId},
{ subscribers.level: newStatus }, function(err, result) {}
);

Resources