I'm trying to add an embedded document to an existing document field. I found one fitting answer with the search but I'm running into errors. I'm using node.js, Express and Mongoose.
My database schemas:
var entry = new Schema({
name : { type : String, required : true},
description : { type : String, default: ""},
});
var compo = new Schema({
name : String,
description : String,
entries : [entry]
});
And I'm trying to update the entries array with the following code
var entry = new entryModel();
entry.name = "new name";
entry.description= "new description";
compoModel.findOne(query, function (err, item) {
if (item) {
item.entries.push(entry);
item.save(function (err) {
if (!err) {
log.debug('Entry added successfully.');
} else {
log.error("Mongoose couldn't save entry: " + err);
}
});
}
});
It yields an error: TypeError: Object.keys called on non-object
What have I missed?
So I managed to get it working via the Model.update method by simply adding a new object to the compo.entries list and calling compoModel.update.
My similar issue (same error) was solved by clearing the sub-document array. It was populated prior to the definition of the sub-document scheme. At least this is what i think happened.
E.g.:
var token = new Schema( { value: String, expires: Date } )
var user = new Schema( { username: String, tokens: [token] } )
.. and, prior to defining the 'token' scheme i had entries such as:
{ username: 'foo', tokens: ['123456'] }
.. so, clearing tokens did it for me.
user.tokens = []
user.save()
Related
I am trying to create a service that can be used to update nested fields in a Mongoose model. In the following example I am trying to set the field 'meta.status' to the value 2. This is the service:
angular.module('rooms').factory('UpdateSvc',['$http', function($http)
{
return function(model, id, args)
{
var url = '/roomieUpdate/' + id;
$http.put(url, args).then(function(response)
{
response = response.data;
console.log(response.data);
});
}
}]);
This is how it is called in the controller:
var newStatus = {'meta.$.status' : 2};
var update = UpdateSvc("roomie", sessionStorage.getItem('userID'), newStatus);
And this is the model:
var RoomieSchema = new Schema(
{
meta:
{
created:
{
type: Date,
default: Date.now
},
status:
{
type: Number,
default: '1',
}
}
}
And this is the route:
app.put('/roomieUpdate/:id', function(req,res)
{
var id = req.params.id;
Roomie.findOneAndUpdate(
{_id: mongoose.Types.ObjectId(id)},
req.body,
{ new : true },
function(err, doc)
{
if(err)
{
console.log(err);
}
res.json(doc);
console.log(doc);
});
});
The argument is received correctly, but I can't seem to get this to work. I am not even getting an error message. console.log(doc) simply prints out the object and the field meta.status remains '1'. I have done a direct Mongo search on the target object to make sure that I wasn't just reading the old document. I've tried a great many things like separating the key and value of req.body and use {$set:{key:value}}, but result is the same.
findOneAndUpdate() by default will return the old document, not the new (updated) document.
For that, you need to set the new option:
Roomie.findOneAndUpdate({
_id : mongoose.Types.ObjectId(id)
}, req.body, { new : true }, function(err, doc) {
...
});
As it turns out, var newStatus = {'meta.$.status' : 2}; should have been var newStatus = {'meta.status' : 2}; The document now updates correctly.
The reason the $ was there in the first place was probably based on this thread:
findOneAndUpdate - Update the first object in array that has specific attribute
or another of the many threads I read through about this issue. I had tried several solutions with and without it, but couldn't get anything to go right.
I'm trying to add comments to this scheme:
var CommentSchema = new Schema(
{
ad_id:String,
comments:
[
{
author: String,
authorID: Number,
posted: Date,
text: String,
title: String
}
]
}
But if the ad_id exists i just want to push the new comment to comments basically create a upsert query:
var query = {'ad_id': req.body.data.ad_ID}
var doc = {$set:{'ad_id':req.body.data.ad_ID},$push:{'comments':{'author':req.body.data.author,'authorID':req.body.data.uID,'posted':req.body.data.posted
,'text':req.body.data.text, 'title':req.body.data.title}}};
var options = {upsert:true};
Comments.findOneAndUpdate(query,doc,options, function (err, doc) {
if(err) {
console.log(String(err));
res.send({"foo": String(err)});
}
else {
console.log((doc));
res.send(doc);
}
});
But in the get the follwing error :
Unable to invalidate a subdocument that has not been added to an array.
It turns out that I had data type error that causes that.
If anyone encounter that error code make sure that the data types match the input.
I have a Mongoose model that looks like this:
var ProjectSchema = new Schema({
name: String,
slug: String,
dateCreated: { type: Date, default: new Date() },
dateUpdated: { type: Date, default: new Date() },
createdByUserId: Schema.Types.ObjectId,
screens: [Schema.Types.Mixed]
});
I have a class method that looks like this:
ProjectSchema.statics.saveElementProperties = function(slugName, screenIndex, elementId, props, callback) {
var Project = mongoose.model('Project');
var updateProject = function(project) {
// Init empty objects if missing
project.screens[screenIndex] = project.screens[screenIndex] || {};
project.screens[screenIndex].elements = project.screens[screenIndex].elements || {};
project.screens[screenIndex].elements[elementId] = project.screens[screenIndex].elements[elementId] || {};
// Apply properties
project.screens[screenIndex].elements[elementId] = "Dummy Project Data";
console.log('elements before save:', project.screens[screenIndex].elements);
project.save(callback);
};
Project.findOne({ slug: slugName }, function(err, project) {
if (!project) {
project = new Project({ name: slugName, slug: slugName });
}
updateProject(project);
});
};
This happens when I call the method saveElementProperties:
The first time I run this method, it works like it should; a new object is added to project.screens[screenIndex].elements both in runtime (the 'elements before save:' log statement) and when checking the MongoDB database with the mongo client.
The second time, a 2nd object is added to project.screens[screenIndex].elements in runtime, but this object is never persisted to MongoDB.
The third time, object 1 and 3 are visible in project.screens[screenIndex].elements in runtime, but the 3rd object is never persisted to MongoDB.
What causes this behavior?
MAJOR UPDATE: I rewrote the entire persistence mechanism to run less frequently, and instead replace the entire project.screens[screenIndex].elements object with an updated structure:
ProjectSchema.statics.saveScreenProperties = function(slugName, screenIndex, elements, callback) {
console.log('saveScreenProperties:', slugName, screenIndex);
var Project = mongoose.model('Project');
var updateProject = function(project) {
// Init empty objects if missing
project.screens[screenIndex] = project.screens[screenIndex] || {};
project.screens[screenIndex].elements = elements;
// Mark as modified and save
project.markModified('screens.' + screenIndex);
project.save(callback);
};
Project.findOne({ slug: slugName }, function(err, project) {
if (!project) {
project = new Project({ name: slugName, slug: slugName });
console.log(' creating new project');
}
updateProject(project);
});
};
However, it still shows the same behavior - it stores the initial elements object, but not later changes to it.
The problem is that you are manipulating the objects just as you would in standard code but mongoose is not a 'persistence engine` it's an ODM. As such you need to use the provided methods to update the datastore.
You will need to use the update operators to $push onto the array. Mongoose has some analogous methods as well. The most explainatory docs are on the MongoDB site.
http://docs.mongodb.org/manual/reference/operator/update-array/
http://docs.mongodb.org/manual/reference/operator/update/
http://docs.mongodb.org/manual/reference/method/db.collection.update/#db.collection.update
I have the following schemas:
var TagSchema = new Schema({
name: {
type: String,
default: ''
}
});
var SnippetSchema = new Schema({
title: {
type: String,
default: ''
},
tags: [{
type: Schema.ObjectId,
ref: 'Tag'
}]
});
I create a new Snippet and search for a tag (got the tagid from the request) to add it to the list of tags.
var snippet = new Snippet(req.body);
Tag.findById(req.body.tagid).exec(function(err, tag) {
snippet.tags.push(tag);
snippet.save(function(err) {
if (err) {
} else {
res.json(snippet);
}
});
});
but i always get the following error:
Cast to ObjectId failed for value "[object Object]" at path "tags"
i tried the same thing with tags being a single tag instad of an array of tags - and that worked perfectly.
does anyone have an idea?
thanks a lot!
I'm pretty sure the error is caused by this line:
var snippet = new Snippet(req.body);
My guess would be that req.body.tags is an object and not an array of objects. When you subsequently save the snippet, the error is thrown.
As a quick fix, you can wrap it with an array:
var tags = req.body.tags;
if (tags && tags.constructor.name !== 'Array') {
req.body.tags = [ tags ];
}
var snippet = new Snippet(req.body);
EDIT: #damphat correctly commented that Mongoose will try and convert non-array values to an array, if that's what's defined in the schema. However, you still need to make sure that the array that's being created is a valid one.
I am working with Twitter authentication and want to store the twitter id as unique key in mongodb. However i see multiple entries with the same id. Here's my schema and code
Schema:
var TwitterSchema = new Schema({
accessToken: String,
accessTokenSecret: String,
name: String,
twitterId: { type: String, required: true, index: { unique: true, sparse: true } }
});
Code:
mongoose.connect('mongodb://localhost/twd')
mongoose.model('Post', TwitterSchema);
var Post = mongoose.model('Post');
var post = new Post();
post.accessToken = accessToken
post.accessTokenSecret = accessTokenSecret
post.name = twitterUserData.name
post.twitterId = twitterUserData.id
post.save(function(err){
if (err){
throw err;
promise.fail(err);
}
console.log('saved');
mongoose.disconnect();
});
promise.fulfill(post);
DB shell output
> db.posts.find();
{ "twitterId" : "21475255", "name" : "MMMK", "accessTokenSecret" : "ZYhiXMWfXvSr1aaCB93hgU243j8aapP0ALdSFlWEE", "accessToken" : "22475255-9YvKMceUInUIxcEtKAK0oMRRG2ZZxn5c52vnwPw", "_id" : ObjectId("4feddf6155203990e000001") }
{ "twitterId" : "21475255", "name" : "MMMK, "accessTokenSecret" : "ZYhiXMWfXvSr1aaCB93hgU2438aapP0ALdSFlWEE", "accessToken" : "22475255-9YvKMceUInUIxcEtKAK0oMRRG2ZZxn5c52vnwPw", "_id" : ObjectId("4feddf7b5905a1a10e000001") }
My guess is either the index isn't being created in MongoDB, or the index with the same name already exits. If it already exists, mongoose will use ensureIndex to create it, but that won't override/redefine it if it already exists. Use the mongo js shell to see if it exists, then try dropping it, restarting mongod, and running your node.js code again.
http://www.mongodb.org/display/DOCS/Indexes#Indexes-CreationOptions