How to find and update subdocument within array based on parent property - node.js

I need to find a subdocument which is inside an array, based on its own user_id property and its parent's my_id property
These are the schemas I have for my model (Conversation):
var listSchema = mongoose.Schema({
user_id: Number,
property: Boolean,
});
var conversationSchema = mongoose.Schema({
my_id: Number,
list: [listSchema]
});
After browsing through mongoose's documentation and some similar questions here on SO, I've tried the following (which seems to me like that it should work):
Conversation.findOneAndUpdate(
{ "my_id": 8, "list.user_id": 16 },
{
"$set": {
"list.$.property": false
}
},
function(err, doc) {
console.error("Error:", err);
console.log("Doc:", doc);
}
);
But it gives me this output and the documents are not being updated at all:
Error: null
Doc: null
What am I missing here? Any help is appreciated, thanks in advance!

I've finally figured out what was going on:
I was saving the user_id property as a String to MongoDB so I tried changing this part of the query to:
"list.user_id": "16"
instead of:
"list.user_id": 16
Still didn't work, but then I figured out that I was defining user_id as Number in listSchema so I changed that to String:
var listSchema = mongoose.Schema({
user_id: String,
property: Boolean,
});
And now it works, it doesn't matter if I use "16" or 16 in the query, I think that mongoose will try to find a String now instead of a Number like it was doing before

Related

mongodb query events done to an item and group them into an array

I know that the title might be a bit vague, but I will elaborate here.
basically what I am trying to achieve is the following:
I have a collection with documents that have the following scheme:
bookId: <uuid>
genre: <string>
isTaken: true
historyIndex: each time something happens i increment this
returnedAt: not required but will be present on documents with historyIndex
takenAt: not required but will be present on documents with historyIndex
there are documents with no historyIndex field because they are representing the book itself without the action that were done to the book.
what i want to do is this:
I want to query the books by their unique uuid and then use the documents with historyIndex and add them to the main documents as in an array as called bookEvents
so the final results will be
bookId:
bookEvents: [] --> an array with all the entries that contain history index
basically everytime the status of the book changes, i am inserting an event with the date it was taken on and the date it was returned on
What would be the best way of achieving such thing ?
Should I query once and iterate in my code ?
Should I query twice (once where the fields exist and once where they don't) ?
Thank you in advance for all the people that will try to help!
You can use the plugin or events to achieve this.
var CounterSchema = new mongoose.Schema({
_id: {type: String, required: true},
seq: { type: Number, default: 0 }
});
var counter = mongoose.model('counter', CounterSchema);
var entitySchema = mongoose.Schema({
sort: {type: String}
});
entitySchema.pre('save', function(next) {
var doc = this;
counter.findByIdAndUpdateAsync({_id: 'entityId'}, {$inc: { seq: 1} }, {new: true, upsert: true}).then(function(count) {
console.log("...count: "+JSON.stringify(count));
doc.sort = count.seq;
next();
})
.catch(function(error) {
console.error("counter error-> : "+error);
throw error;
});
});
refer https://stackoverflow.com/a/40137723/8201020

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.

Revert to default value in mongoose if field is set to null

I am using mongoose with nodeJS. Consider the following schema:
var PersonSchema = new mongoose.Schema({
"name": {type: String, default: "Human"},
"age": {type: Number, defualt:20}
});
mongoose.model('Person', PersonSchema);
var order = new Order({
name:null
});
This creates a new Person document with name set to null.
{
name:null,
age: 20
}
Is there anyway to check if the property being created/updated is null and if it is null set it back to default. The following declaration
var order = new Order();
order.name = null;
order.save(cb);
should create a new Person document with name set to default.
{
name:"Human",
age: 20
}
How would one implement this using NodeJs and mongoose.
Well there are a few way to go about this:
PRE-SAVE/PRE-VALIDATE hooks
Mongoose Middleware hooks
Middleware (also called pre and post hooks) are functions which are passed control during execution of asynchronous functions.
PersonSchema.pre('save', function(next) {
if (this.name === null) {
this.name = 'Human';
}
next();
});
ENUM
Mongoose Validation and Mongoose Enums
Strings have enum, match, maxlength and minlength validators.
var PersonSchema = new mongoose.Schema({
"name": {type: String, default: "Human", enum: ['Human', 'NONE-Human']},
"age": {type: Number, defualt:20}
});
Update 1
If I have 20 fields where I want to check whether they are set to null, do I have to repeat this.field = default for all of them?
I guess you would have to.
What does NONE-Human in enums do? I could not find documentation for this online.
That is just an example of ENUM with enum you can only choose values that are specified in ENUM i.e. 'Name' can only have values of 'Human' or 'NONE-Human'.
A little late to answer this, but a more generic approach would be not to hardcode the fields again in the pre hook.
Instead let's use the schema itself to go over the fields and check for the fields that have default values and are explicitly set to null. In this case we set it to the default value.
PersonSchema.pre('save', function(next) {
const self = this;
Object.keys(this.schema.paths).forEach(function(key) {
if(self.schema.paths[key].options.default && self[key] === null) {
self[key] = self.schema.paths[key].options.default;
}
});
next();
});
An even later answer. I was looking for a simpler solution and came across this question. Thought I'd share what I ended up doing.
db.Person.create({
name: usersName || "Human",
age: usersAge,
})

mongoose: Insert a subdocument NOT an array

I am struggling to insert a document inside another document. I've looked at all the entries like this but they aren't quite what I am looking for.
Here is the scenario:
I have a common document that has its own schema. Lets call it a related record:
(function(){
'use strict';
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var relatedRecordSchema = new Schema({
params: {
recordId: Schema.Types.ObjectId,
recordType: String,
recordTitle: String
},
metadata: {
dateCreated: {type: Date, default: Date.now}
}
},{ _id : false });
mongoose.model('RelatedRecord', relatedRecordSchema);
})();
I have no trouble inserting this in an ARRAY inside document that require it. I.e its configured this way:
//Embedded
relationships: {
following: [mongoose.model('RelatedRecord').schema],
followers: [mongoose.model('RelatedRecord').schema],
blocked: [mongoose.model('RelatedRecord').schema]
}
This works perfectly.
The scenario that does not work is where there is a single related record, lets say the source of a notification:
var notificationSchema = new Schema({
params: {
title: String,
imageUrl: String,
source: mongoose.model('RelatedRecord').schema
},
metadata: {
dateCreated: { type: Date, default: Date.now },
dateViewed: Date
}
});
So when I am creating the notification I try and assign the previously prepared RelatedRecord
returnObj.params.source = relatedRecord;
The record appears during a debug to be inserted (it is inside a _docs branch but far deeper than I would expect) but when the object is saved (returnObj.save()) the save routine is abandoned without error, meaning it does not enter into the callback at all.
So it looks to me that i'm confusing mongoose as the dot assignment is forcing the subdoc into the wrong location.
So the question is simple:
How do I set that subdocument?
What the question isn't:
No I don't want to populate or advice on how you would solve this problem differently. We have sensible reasons for doing things how we are doing them.
Cheers
b
As Hiren S correctly pointed out:
1) Sub-Docs = array, always. Its in the first line in the docs :|
2) By setting the type to mixed, assignment of the object worked.
I'm a dumdum.

Not able to persist array of objects in mongo using mongoose

I'm trying to persist an array of objects in a document using mongoose. I have tried multiple times but it's not persisting array in document. It places an empty array in document.
Following is my Schema:
var ProfileSchema = new Schema({
name: String,
PagesData: [{
pageAccessToken: {type: String, get: decryptText, set: encryptText},
category: String,
name: String,
id: String,
perms: [String]
}]
});
module.exports = mongoose.model('Profile', ProfileSchema);
I'm trying to save a document with an array of objects using following query:
var newProfile = new Profile();
newProfile.name = "someName";
newProfile.PagesData = [ { pageAccessToken: 'someToken',
category: 'Bags/Luggage',
name: 'someBrandName',
id: '12345',
perms:
[ 'ADMINISTER',
'EDIT_PROFILE',
'CREATE_CONTENT' ] } ];
newProfile.save(function(err, result, numAffected){
if(err) {
console.log(err);
res.send(500, "Error");
}
console.log(result);
res.send(200, "Success");
});
I tried debugging the mongo commands using
require('mongoose').set('debug', true)
On Debug logs it shows, empty array during insert command execution.
Can anyone please tell me how can I store this array of object in my schema ?
Thanks,
Update:
It's been too long and I'm still not able to figure out the root cause of the problem. There is a long thread going on github for this.
https://github.com/Automattic/mongoose/issues/3249
I would like other experts to please take a look and suggest me some way by which I can solve the issue. I'm really stuck at this.
Update 2:
None of the solution worked for me so far, so I decided to modify the schema only to meet my requirements. This resulted in a different problem:
I want to create a map with a objectId as key and an array of string values as its value. The closest that I can get is:
var schema = new Schema({
map: [{myId: {type:mongoose.Schema.Types.ObjectId, ref: 'MyOtherCollection'}, values: [String]}]
});
But somehow this is not working for me. When I perform an update with {upsert: true}, it is not correctly populating the key: value in the map. In fact, I'm not even sure if I have declared the schema correctly.
Can anyone tell me if the schema is correct ? Also, How can I perform an update with {upsert: true} for this schema?
Also, if above is not correct and can;t be achieved then how can I model my requirement by some other way. My use case is I want to keep a list of values for a given objectId. I don't want any duplicates entries with same key, that's why picked map.
Please suggest if the approach is correct or should this be modelled some other way?
Thanks
I tried the exact code you have provided here and it's working for me. I am not sure what is causing the issue for you. Until and unless we get the same issue, it's very difficult to rectify it.
Here are few suggestions which you might try:
Create a simple schema and try storing the object, that way you can
figure it out if it has to do something with the schema.
You can try out your schema in a sample app to find if some
dependency is causing the problem.
Once you know where exactly the problem is, you would be able to figure out a solution too. I hope it helps.
I tested this and the insert works for me using the below:
(I had to remove the get: decryptText, set: encryptText)
var n = { name: "Testing for mongoose", PagesData : [{ pageAccessToken: 'someToken',
category: 'Bags/Luggage',
name: 'someBrandName',
id: '12345',
perms:
[ 'ADMINISTER',
'EDIT_PROFILE',
'CREATE_CONTENT' ] } ] }
Profile.create(n, function (err) {
if (!err) {
return 'records saved successfully';
}
else {
return error on save:' + err;
}
});
To create multiple pageDatas you can use it as an embedded collection instead of using arrays.
The Schema will be as follows:
var PagesDataSchema = new Scheme({
pageAccessToken: {type: String, get: decryptText, set: encryptText},
category: String,
name: String,
id: String,
perms: [String]
})
var ProfileSchema = new Schema({
name: String,
PagesData: [PagesDataSchema]
});
module.exports = mongoose.model('Profile', ProfileSchema);
Reference: http://mongoosejs.com/docs/subdocs.html
For Saving the document you can use like.
exports.save = function(req,res){
var test = new ProfileSchema; // new object for ProfileSchema domain.
test.name= req.body.name;
if(req.body.PagesData){
req.body.PagesData.forEach(function(page){ // For every element of pageData from client.
test.PagesData.push(page) // This pushes each and every pagedata given from the client into PagesData.
})
}
test.save(function (saveErr, saved) { // Saves the new document into db.
if (saveErr) {
console.log(saveErr)
return;
}
res.status(HttpStatus.OK).json(saved);
});
};
Hope this helps.
Have you tried
Profile.create({
name: "someName",
PagesData: [
{
pageAccessToken: 'someToken',
category: 'Bags/Luggage',
name: 'someBrandName',
id: '12345',
perms: [
'ADMINISTER',
'EDIT_PROFILE',
'CREATE_CONTENT'
]
}
]
}, function(err, profile) {
// do your stuff
})
?

Resources