mongodb won't find my schema method in nested container - javascript

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}
});

Related

Updating a schema within a schema Mongoose

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' } } )

If value of a property is null when updating then that property should not be added to the record

Suppose I have a mongoose schema like this:
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var testSchema = new Schema({
name: {type: String, required: true},
nickName: {type: String}
});
var Test = module.exports = mongoose.model('Test', testSchema);
I declare methods for CRUD operation using variable Test. From that one such method is update, which is defined as follows:
module.exports.updateTest = function(updatedValues, callback) {
console.log(updatedValues); //this output is shown below
Test.update(
{ "_id": updatedValues.id },
{ "$set" : { "name" : updatedValues.name, "nickName" : updatedValues.nickName } },
{ multi: false },
callback
);
};
Now, I use this method inside my node router as follows:
router.put('/:id', function(req, res, next) {
var id = req.params.id,
var name = req.body.name,
var nickName = req.body.nickName
req.checkBody("name", "Name is required").notEmpty();
var errors = req.validationErrors();
if(errors) { ........ }
else {
var testToUpdate = new Test({
_id: id,
name: name,
nickName: nickName || undefined
});
Test.updateTest(testToUpdate, function(err, result) {
if(err) { throw(err); }
else { res.status(200).json({"success": "Test updated successfully"}); }
});
}
});
Now if I save a new record in database and then see it in database then it looks like:
{
"_id" : ObjectId("ns8f9yyuo32hru0fu23oh"), //some automatically generated id
"name" : "firstTest",
"__v" : 0
}
Now if I update the same document without changing anything and then if I take a look at same record in database, then I get:
{
"_id" : ObjectId("ns8f9yyuo32hru0fu23oh"), //some automatically generated id
"name" : "firstTest",
"__v" : 0,
"nickName" : null
}
Can you see that nickName is set to null? I don't want it to work like this. I want that if my property is null, then that property should not be included in the record.
If you remember, I have console logged the updatedValues before updating it. (see the second code block in question). So, here is the logged values:
{
"_id" : ObjectId("ns8f9yyuo32hru0fu23oh"), //some automatically generated id
"name" : "firstTest"
}
I don't know why, but nickName is not present in the logged values and then after update I get nickName: null. I think, the problem lies in second Code block. Can you please check it?
Note:
Actually I have lot more fields in my schema than I specified in question. Some fields are reference to other records as well.
You can prevent such documents from updating in MongoDB by setting the runValidators option to true in the update method.
Ex:
module.exports.updateTest = function(updatedValues, callback) {
Test.update(
{ "_id": updatedValues.id },
{ "$set" : {
"name" : updatedValues.name, "nickName" : updatedValues.nickName } ,
},
{ multi: false, runValidators: true },
callback
);
};
In addition, you can also set the option omitUndefined to true to prevent undefined values from being reflected.
Ex:
module.exports.updateTest = function(updatedValues, callback) {
Test.update(
{ "_id": updatedValues.id },
{ "$set" : {
"name" : updatedValues.name, "nickName" : updatedValues.nickName } ,
},
{ multi: false, runValidators: true, omitUndefined: true },
callback
);
};
I wouldn't write this this way, but I'll tell you why your code is failing.
The problem is your $set block
You're choosing to specifically set the value to the update object passed in. If the value is undefined you're forcing mongo to set that to null.
Here's the problem
example, in DB:
{
"_id" : ObjectId("ns8f9yyuo32hru0fu23oh"),
"name" : "firstTest",
"nickname": "jack",
"__v" : 0
}
IF you pass in testToUpdate = { name: 'foo' } you'll end up with
Test.update({ ... }, { $set: { name: 'foo', nickname: undefined }}
because you're getting updatedValues.nickname off of the arguments and thats not defined
What you want is
Test.update({ ... }, { $set: updatedValues }
which is translated to
Test.update({ ... }, { $set: { name: 'foo' } }
You're no longer providing a key for nickname, thus not making it set to undefined/null.
I would use a mongoose plugin and not worry about manually passing the fields all the way to your model (see github.com/autolotto/mongoose-model-update)
You can define the update-able fields and then you can just do model.update(req.body) and not worry about all this
Even if you don't want to use the plugin you can still just do Test.findByIdAndUpdate(id, { name, nickname }, callback)
Its true that the problem is your $set part as pointed in the other answers, but using if condition is not the best way to do this. What if there are multiple fields you might need multiple if conditions or even a separate function to deal with this.
Mongoose offers a really good option to deal with this:{omitUndefined: 1} , it will not update all the undefined values in your query.
Taking a look at your code, there is a problem in your update method that won't help you to obtain the result you want.
This is you update code:
module.exports.updateTest = function(updatedValues, callback) {
console.log(updatedValues); //this output is shown below
Test.update(
{ "_id": updatedValues.id },
{ "$set" : { "name" : updatedValues.name, "nickName" : updatedValues.nickName } },
{ multi: false },
callback
);
};
The problem is in the $set part.
In mongodb you cannot unset a field in a document by just assigning undefined to it. You should use the $unset operator.
So your update code should be something like:
module.exports.updateTest = function(updatedValues, callback) {
console.log(updatedValues); //this output is shown below
const operators = {$set: {name: updatedValues.name}};
if (updatedValues.nickName) {
operators.$set.nickName = updatedValues.nickName;
} else {
operators.$unset = {nickName: 1};
}
Test.update(
{ "_id": updatedValues.id },
operators,
{ multi: false },
callback
);
};
Note that the use of $unset is fundamental to remove the field if it already exists in your document.
As you're using $set in the query which says in the documentation
The $set operator replaces the value of a field with the specified value.
So you're forcing to set the undefined value which is null here to the field. Either you set required : true against the nickName field in the schema or try passing JS object instead of writing raw query like,
Instead of :
Test.update(
{ "_id": updatedValues.id },
{ "$set" : { "name" : updatedValues.name, "nickName" : updatedValues.nickName } },
{ multi: false },
callback
);
Try doing:
var data = { name : updatedValues.name };
if(updatedValues.nickName){
data.nickName = updatedValues.nickName;
}
Model.update({ "_id": updatedValues.id }, data ,options, callback)
Check this link in Mongoose documentation for more information on the approach.
The problem is that you are still adding the nickname property on the document - you're just setting it to undefined, but contrary to what you thought that's not the same as not setting it at all.
What you want is to not include the nickname property at all if it's undefined, which you can do like this:
module.exports.updateTest = function(updatedValues, callback) {
const set = { "name": updatedValues.name };
if (updatedValues.nickName) {
set["nickName"] = updatedValues.nickName;
}
Test.update({ "_id": updatedValues.id }, { "$set": set}, { multi: false },
callback
);
};
The easiest method I found is by using lodash.pickby you just pass body (or any object for that matter) and it removes all undefined and null fields and keys

Populate document if there is no value, using MongooseJS

Let's say I have a User model like this
var userSchema = new Schema({
username : String,
email : String,
project : {type : String, ref : "Project"}
});
and a User document like this.
{
"_id" : ObjectId("56df56c58a4d47c83bf41603"),
"username" : "user1",
"email" : "email#example.com",
"project" : "",
"__v" : 1
}
If I do the following, the page never loads.
User.findById("56df56c58a4d47c83bf41603").populate("project").exec()
.then(function(userObj) {
res.render('user', {
user : userObj
});
});
It works fine if there is an actual ObjectID in there, but not when it is blank.
Is there a way that I can default to null if there is no ObjectID in the value?
So the answer to the question is here:
Mongoose: CastError: Cast to ObjectId failed for value "[object Object]" at path "_id"
An empty string will throw a cast error. Your weren't trapping the exception from the promise based call and so your route was timing out.
The lesson is to trap the exception. Just like you would if you were using callbacks.

Nested Arrays in Mongoose with ObjectId

I am trying to record the id's of the posts that the user has voted on, and I am creating a schema for it like this:
var userSchema = new Schema({
twittername: String,
twitterID: Number,
votedPosts: [{ObjectId : {votetype : Number}} ]
});
The user gets created perfectly and I want to update the votedPosts attribute whenever the user votes on something so I call :
User.update({twitterID : req.body.userID} , { $push : {votedPosts : {postID : {votetype: newvotetype }}}} ,function (err, user, raw) {
if (err){
console.log(err);
}
else{
console.log('user ' + user);
}
});
Unfortunately the outcome is not as I described in my schema, I get this:
db.users.find().pretty()
{
"__v" : 0,
"_id" : ObjectId("51bf4ef8dbda2f2e0c000001"),
"twitterID" : 102016704,
"twittername" : "gorkemyurt",
"votedPosts" : [
{
"_id" : ObjectId("51bf5e48c3ffefe20c000002")
}
]
}
I am confused by the extra "_id" that mongoose adds in the votedPosts array.. Why cant ObjectId("51bf5e48c3ffefe20c000002") be the key of a key value pair?
I think your problem stems from your schema. You should try to define a schema in which the keys are not dynamically changing. MongoDB does not support queries on the keys of documents, just the values.
I think that using a dynamic "ObjectID" as the key in your schema above is leading to weirdness with Mongoose, and causing your issue. But, even if your query were propagated properly to MongoDB, it would still not produce the output you desire. The reason for this is that MongoDB will interpret "postID" as a String, regardless of whether you've defined some postID variable to hold a dynamic value. This is the output if you run the query from the mongo shell, using a variable postID:
> var postID = 1234
> db.users.update( {twitterID : 102016704}, {$push : {votedPosts : {postID : {votetype: "newvotetype" }}}} )
{
"__v" : 0,
"_id" : ObjectId("51bf4ef8dbda2f2e0c000001"),
"twitterID" : 102016704,
"twittername" : "gorkemyurt",
"votedPosts" : [
{
"postID" : {
"votetype" : "newvotetype"
}
}
]
}
I would suggest that you use another schema. The schema I've proposed below involves embedding a second schema into your existing schema. Try something like this:
var votedPost = new Schema({
postID: ObjectId,
votetype : Number
});
var userSchema = new Schema({
twittername: String,
twitterID: Number,
votedPosts: [votedPost]
});
This way, you can also query on the postID field, which MongoDB handles very nicely. Does that make sense? :)

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