Updating a schema within a schema Mongoose - javascript

Been trying to attempt to update a field within a nested array in my document.
Such as an example schema here..
childSchema = new Schema ( {
foo2 : String,
bar2 : String,
foobar : String
) };
Within this schema
parentSchema = new Schema ( {
foo1 : String,
bar1 : String,
nested : [childSchema]
)};
If I wanted to update bar2 (childSchema) based on foo2 matched string how would I go about this?
I have tried the following,
parentSchema.childSchema.updateOne( { 'nested.foo2' : 'string-that-matches' }, { $set: { 'nested.$.bar2' : 'new-string-for-bar2' } } )
I typically get the error
TypeError: Cannot read property 'updateOne' of undefined
I used to not have the childSchema like that, it was just made in the parentSchema. It was more for testing on separating them out.
I am sorry if I formatted this question wrong, and obviously I tried to do a mock schema set from my real thing. I figured that it's something more with my query than the set up.
Thanks!

parentSchema is enough to this update
parentSchema.updateOne( { 'nested.foo2' : 'string-that-matches' }, { $set: { 'nested.$.bar2' : 'new-string-for-bar2' } } )

Related

Return value of static method of nested model is ignored in parent model (expressjs)

I have a model in expressjs that has a property which is an instance of another model, basically a nested model, like this:
// nested.js
var NestedPropDef = {
someProp: { type: String, default: '' },
};
var schema = new Schema(NestedPropDef, { minimize: false });
schema.statics = {
getInstance() {
var nestedObject = new NestedProp();
nestedObject.someProp = 'something';
console.log('[first log] value of nestedObject:', nestedObject);
return nestedObject;
},
});
var NestedProp = mongoose.model('NestedProp', schema);
exports.NestedPropDef = NestedPropDef;
exports.NestedProp = NestedProp;
// parent-file.js
var NestedPropDef = require('./nested').NestedPropDef;
var NestedProp = require('./nested').NestedProp;
var schema = new Schema({
otherProp: { type: String, default: '' },
nestedProp: NestedPropDef,
});
schema.methods.updateNestedProp = function (data, callback) {
this.nestedProp = NestedProp.getInstance();
console.log('[second log] value of nestedProp:', this.nestedProp);
this.otherProp: data.otherProp;
console.log('[third log] value of this:', this);
this.save(callback);
});
var Parent = mongoose.model('Parent', schema);
module.exports = Parent;
Each of those console.log statements yield the following:
[first log] value of nestedObject: {
someProp: 'something'
}
So I know the instance of the nested property is being created correctly.
[second log] value of nestedProp: {}
I don't understand why this is an empty object. For some reason, the value returned by .getInstance is not saved to this.nestedProp.
[third log] value of this: {
otherProp: 'some value'
}
I don't understand why nestedProp is completely missing from this object.
So basically I can't figure out why the return value from the static method of the nested object is not getting used by the parent model. Any ideas will be very welcome, thanks!
Update: it appears that the bug is linked to using this JWT library, though I don't know why. I don't use the library in any routes/code related to this problem. I think they're linked because the bug goes away when I revert to the commit before I installed the JWT library.
Update 2: the JWT lib was actually unrelated to the issue, but the problem was introduced with a recent change to mongoose. As of mongoose#5.9.24, the log statements would be as follows (and this is what I want):
[first log] value of nestedObject: {
someProp: 'something'
}
[second log] value of nestedProp: {
someProp: 'something'
}
[third log] value of this: {
otherProp: 'some value',
nestedProp: {
someProp: 'something'
}
}
I know that I can export the schema of NestedProp instead of its definition, and that would mostly fix the issue, but it adds the _id field to nestedProp and that wasn't happening before. In other words, doing this:
// nested.js
...
exports.NestedPropSchema = schema;
exports.NestedProp = NestedProp;
// parent-file.js
var NestedPropSchema = require('./nested').NestedPropSchema;
var NestedProp = require('./nested').NestedProp;
var schema = new Schema({
otherProp: { type: String, default: '' },
nestedProp: NestedPropSchema,
});
...
causes this:
[third log] value of this: {
otherProp: 'some value',
nestedProp: {
someProp: 'something',
_id: ...
}
}
I know there's a way to disable the automatic addition of the _id, but my objective is to restore the previous functionality so that other subtle bugs don't creep up on me. How can I restore the results I was seeing in 5.9.24? Or was I always nesting incorrectly (and Mongoose was just working with my incorrect nesting)?

Is it possible to have mongoose populate a Mixed field only if the field contains an ObjectId?

Setup
Let's say we have schemas for Foo and Bar. Foo has a field bar. This field can either contain an ObjectId that reference a document Bar, or it could contain other arbitrary objects that are not ObjectIds.
const fooSchema = new mongoose.Schema({
bar: {
type: mongoose.Schema.Types.Mixed,
ref: 'Bar'
}
});
const Foo = <any>mongoose.model<any>('Foo', fooSchema);
const barSchema = new mongoose.Schema({
name: String
});
const Bar = <any>mongoose.model<any>('Bar', barSchema);
Problem
Now suppose we have a bunch of Foo documents.
I would like to be able to use mongoose's populate on the bar field to automatically replace references to a Bar document with the actual Bar document itself. I would also like to leave all other objects that are not references to a Bar document unchanged.
Normally, I would use something like this to get all the Foo documents and then populate the bar field:
Foo.find().populate('bar')
However, this method will throw an exception when it encounters objects in the bar field that are not ObjectIds, as opposed to leaving them untouched.
UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): CastError: Cast to ObjectId failed for value "Some arbitrary object" at path "_id" for model "Bar"
Attempt at finding a solution
I have examined using the match option on populate, by requiring that a field on Bar exists:
Foo.find().populate({
path: 'bar',
match: {
name: {
$exists: true
}
}
}
Unfortunately, the error is the same.
Question
So my question is then, is there any way to get mongoose to only populate a field if the field contains an ObjectId, and leave it alone otherwise?
As far as I know you cannot use populate that way. Select property works after trying to get values for population and there's no way to filter it before that.
You would have to do it manually. You could do it manually.
let foos = await Foo.find({});
foos = foos.map(function (f) {
return new Promise(function (resolve) {
if (condition()) {
Foo.populate(f, {path: 'bar'}).then(function(populatedF){
resolve(f);
});
} else {
resolve(f);
}
});
});
await Promise.all(foos).then(function (fs) {
res.status(200).json(fs);
});
Elegantly would be to wrap it in post hook or static method on your Model.
Another option would be to send 2 queries:
const foosPopulated = Foo.find({ alma: { $type: 2 } }).populate('bar'); // type of string
const foosNotPopulated = Foo.find({ alma: { $type: 3 } }); // type of object
const foos = foosPopulated.concat(foosNotPopulated);
This is of course suboptimal because of 2 queries (and all population queries) but maybe this will not be a problem for you. Readability is much better. Of course you could then change find queries to match your case specifically.

How to make Mongoose not insert empty array or object fields into a document

Let's say we have a Mongoose schema in our Node.js project:
let coolSchema = new mongoose.Schema({
field_1 : Number,
field_2 : String,
field_3 : [ String ],
});
And let's we have an according object:
var data = {
field_1 : 123,
field_2 : 'blah',
field_3 : ['aa', 'bb'],
};
Now to save this data into MongoDB we can use this code:
let Model = require('mongoose').model('CoolModel', coolSchema);
(new Model(data)).save();
Ok, while it's all cool.
But if data does not contain field_3 (array field, and the same will be for an object field) Mongoose will anyway add this field into the being created document with empty value.
Can we somehow tell Mongoose not to create this field if it's not contained in the data object?
you can do it easily skip the array field and array of object field.. This will let you skip saving empty array in new documents.but you have to use pre hook for this .
var brandSchema = new Schema({
name : {type:String},
email:String,
check:[]
})
brandSchema.pre('save', function (next) {
if (this.isNew && 0 === this.check.length) {
this.check = undefined;
}
next();
})
when new document is inserted in your schema you have to use this middlware.this works fine so try this.
this is the response when we want to insert any document
"data": {
"__v": 0,
"name": "testing",
"email": "testing#gmail.com",
"_id": "5915b018e292833edda8837f"
}
so i have send only email and name but check(array) field is skipped(Not send any value).
The accepted answer is good. But if you wouldn't want to use pre-hook, then you can add default: undefined to the array fields. For example:
var schema = new Schema({
myArr: { type: [String], default: undefined }
});
Refer to this comment for more explanation.
Not particularly an answer to the question itself but some thought on the matter.
It's not clear exactly what you're trying to achieve here. You defined a schema that is supposed to contain a list of string. Mongoose correctly does so that the data saved in your schema is consistent with the definition of the schema.
In this case, the list is more of a structural part of the schema. If you have different behaviour, you'd have to handle special case in your code in case the list isn't present. Now, you can safely assume that you schema is always returning a list so fetching some data will always allow you to do:
coolData.field_3.forEach(function(x) {
do_cool_things(x)
})
What you're asking is to make the schema allow inconsistent data being returned from mongodb... In other words, you'd have to do this in order to prevent accessing attributes on undefined:
if (coolData.field_3) {
coolData.field_3.forEach(function(x) {
do_cool_things(x)
})
}
Also, I you were trying to optimize the size of you objects/database, you could fill a bug report so mongoose doesn't define empty values while saving the objects and autofill them with defaults when the field is missing from mongodb. (I could be wrong but did you actually check if the data in mongodb was containing empty values or you were just looking at data coming from mongoose?)
It's because you're not marking the fields as required in your schema definition.
Do this:
let coolSchema = new mongoose.Schema({
field_1 : { type: Number, required: true },
field_2 : { type: String, required: true },
field_3 : { type: [ String ], required: true },
});

mongodb won't find my schema method in nested container

I am trying to access the method of a schema which is stored inside a mixed container. Here is the situation :
I have some cases model which can be many different things, so I have a schema for each of these things which are stored in the "caseContent" mixed property.
var CaseSchema = mongoose.Schema({
caseContent : {},
object : {type:String, default : "null"},
collision : {type : Boolean, default : false}
});
The caseContent property is then filled with the model of one of my schemas, like this one for exemple :
var TreeSchema = new mongoose.Schema({
appleCount : {type : Number, default : 3}
});
TreeSchema.methods.doStuff = function (data) {
console.log('Hey, listen');
return true;
};
Then, I want to use the method of my schema from the original container :
CaseSchema.methods.doStuff = function (data) {
if (this.caseContent.doStuff !== undefined) {
this.caseContent.doStuff();
console.log('it worked');
} else {
console.log('doStuff is undefined');
console.log(this.caseContent.doStuff);
}
};
On the first time (when everything is added on the database) it works. Then, the caseContent.doStuff seems to be always undefined (the console.log('doStuff is undefined'); appears each time).
So I think there is something that keeps me from calling that method probably because of the mixed type of the container... Is there any workarround for that ?
You could try to use this schema type Schema.Types.Mixed
var CaseSchema = mongoose.Schema({
caseContent : Schema.Types.Mixed,
object : {type:String, default : "null"},
collision : {type : Boolean, default : false}
});

Expressjs Mongoose find nested embedded documents are undefined

I'm building a game with nodejs 0.6.18 expressjs 2.5.8 and mongoose 2.6.7.
I'm trying to store and retrieve embedded documents with mongoose.
Consider this example :
User schema
var User = module.exports = new Schema({
username: { type: String }
, characters: [Character]
, created: { type: Date, default: Date.now }
});
Character schema
var Character = module.exports = new Schema({
name: { type: String }
, spells: [Spell]
, created: { type: Date, default: Date.now }
});
Spell schema
var Spell = module.exports = new Schema({
name: { type: String }
, created { type: Date, default: Date.now }
});
Mongoose
var db = mongoose.createConnection('mongodb://localhost/mygame');
mongoose.model('User', require('./models/user'));
mongoose.model('Character', require('./models/character'));
mongoose.model('Spell', require('./models/spell')
Route
app.get('/', function(req, res) {
var User = db.model('User')
, Character = db.model('Character')
, Spell = db.model('Spell')
, u = new User({username:'foo'})
, c = new Character({name:'bar'});
c.spells.push(new Spell({name:'fireball'}));
c.spells.push(new Spell({name:'frozenball'}));
u.characters.push(c);
u.save(function(e, s) {
User.find({username:'foo'}, function(err, success) {
console.error(err);
console.log(success);
});
});
});
Console output
null
[ { username: 'foo',
_id: 4fda2c77faa9aa5c68000003,
created: Thu, 14 Jun 2012 18:24:55 GMT,
characters: [ undefined ] } ]
It looks like the User document is correctly saved and retrieved by mongoose. But, associated embedded documents are undefined.
I wanted to be sure that my document is saved, so I've directly asked to mongodb :
$ mongo mygame
> db.users.find({username:'foo'})
{
"username" : "foo",
"_id" : ObjectId("4fda2c77faa9aa5c68000003"),
"created" : ISODate("2012-06-14T18:24:55.982Z"),
"characters" : [
{
"name" : "bar",
"_id" : ObjectId("4fda2c77faa9aa5c68000004"),
"created" : ISODate("2012-06-14T18:24:55.986Z"),
"spells" : [
{
"name" : "fireball",
"_id" : ObjectId("4fda2c77faa9aa5c68000005"),
"created" : ISODate("2012-06-14T18:24:55.987Z")
},
{
"name" : "frozenball",
"_id" : ObjectId("4fda2c77faa9aa5c68000007"),
"created" : ISODate("2012-06-14T18:24:55.990Z")
}
]
}
]
}
As you can see, my documents seems to be correctly stored to mongodb, but I'm unable to retrieve whose who are embedded with mongoose.
I've also tried without the nested embedded Spell document, wich produce the exact same problem.
EDIT
#pat You are right. If I put all the Schemas directly in the same file (the main app.js for exemple) with the right order, it works.
The fact is, I would like to keep each models in separate files as much as possible (they are gonna grow a lot).
For exemple, my User model is contained in a file called models/user.js, and should be accessible using module.exports as above.
But, when I try to link my model to mongoose in an another file : mongoose.model('User', require('./models/user')); the mongoose find method returns undefined embedded documents.
Do you have any ideas on how to properly keep my mongoose models on separate files ?
The user schema file should first require the CharacterSchema at the top, then pass it in:
var CharacterSchema = require('./character');
var User = module.exports = new Schema({
username: { type: String }
, characters: [CharacterSchema]
, created: { type: Date, default: Date.now }
});
Likewise, the CharacterSchema file should first require the SpellSchema at the top, then pass it:
var SpellSchema = require('./spell');
var CharacterSchema = module.exports = new Schema({ spells: [SpellSchema] })
This will retain their order.
Note: subdocs are not really needed to be models, since models are mapped to collections, but as long as you are not calling save directly on your subdocs they won't get saved to a separate collection in the db.
The User schema needs to be declared after Character and Spell, I believe.

Categories

Resources