I'm still getting the hang of Express/Angular and how they work together to post to the server. I'm using the MEAN stack in my sample application.
The Schema for the object I'm trying to post looks like this in Express.
First define the 'Version' Schema:
var VersionSchema = new Schema({
title: {
type: String,
default: '',
trim: true
},
content: {
type: String,
default: '',
trim: true
},
submitted: {
type: Date,
default: Date.now
},
user: {
type: Schema.ObjectId,
ref: 'User'
}
});
The Version Schema is used in my Draft Schema. When a user creates a draft, it becomes a Version item in the versions array. When the draft is updated, it creates a new array item so all versions of the draft are saved.
var DraftSchema = new Schema({
versions: [VersionSchema],
});
The Draft Schema is set up as an Angular Service and injected into my controller. The service is boilerplate code for creating a $resource though so shouldn't be necessary.
I have two controllers to create the Draft object and save to Mongo: one with Angular, one with Express.
Angular controller:
$scope.create = function() {
var version = {
title: this.title,
content: this.content
};
var draft = new Drafts({
versions: [version]
});
draft.$save(function(response) {
$location.path("drafts/" + response._id);
});
};
In the angular controller I'm passing the title and contents to be saved to the Draft. As I understand it, you should not use an Angular controller to pass user information to the server. So this is handled by Express in the controller below.
Express controller:
exports.create = function(req, res) {
var draft = new Draft(req.body);
draft.versions = [req.user]; // Here is where my problem is, trying to save user
draft.save(function(err) {
if (err) {
return res.send('users/signup', {
errors: err.errors,
draft: draft
});
} else {
res.jsonp(draft);
}
});
};
You can see in my comment above where my problem is. I've looked through docs, SO questions, etc for the correct way to set my User property to the Versions array item being created here.
Note that if I move the User from the Version Schema to the Draft Schema and use:
draft.user = req.user;
To save the user then it works just fine. But I want the user to be saved into the VersionSchema.
To address Erick's comment below, this is a JSON representation of what I want my Draft model to look like. Versions is an Array because I want a new array item to be created each time the draft is updated.
[{
_id: 4h55542j232555000441,
versions: [
{
title: “A really great title”,
body: “On that day the people….”,
user: 5234523452349589hh23523,
submitted: Date
}
]
}];
draft.versions = [req.user]
Does the following: replace the versions array containing a version object with a new array containing the user object. This is not what you want.
As I see it, draft.versions has the following content:
[
{
title: '...',
content: '...'
}
]
To add a user id you need to do the following steps:
get the version object from the versions array
assign a new property to the version object and give it a value of user._id
var version = draft.versions[0]
version['user'] = req.user._id
Related
I would like to know the best approach to solve the current scenario.
I've got a node API which uses mongoose and bluebird. And some Android clients will post "movement" entities to it.
(Question at the end).
Let's say movement-model.js exports the Schema, and looks like this:
"use strict";
const mongoose = require('mongoose');
const _movementSchema = {
movementId: { type: Number, requried: true },
relMovementId: Number,
_party: { type: mongoose.Schema.Types.ObjectId, required: true, ref: 'Party' }
}
module.exports = mongoose.Schema(_movementSchema, {collection: 'movement'});
And related exported Schema on party-model.js is as follows:
"use strict";
const mongoose = require('mongoose');
const _partySchema = {
active: { type: Boolean, default: true },
name: { type: String, trim: true, required: true },
logo: { type: Buffer },
coordenates: { lat: Number, long: Number },
startOn: { type: Date, required: true },
endOn: { type: Date, required: true }
}
module.exports = mongoose.Schema(_partySchema, {collection: 'party'});
Android client would send the JSON with ObjectId and not full populated object. So when the POST comes, I'm using it directly (i.e: let _movement = req.body;) and on the movement-dao.js I've got the createNew method and I'm exporting the Model:
"use strict";
const mongoose = require('mongoose');
const Promise = require('bluebird');
mongoose.Promise = Promise;
const movementSchema = require('../model/movement-model');
movementSchema.statics.createNew = (movement) => {
return new Promise((resolve, reject) => {
if (!_.isObject(movement)) {
return reject(new TypeError('Movement is not a valid object.'));
}
let _something = new Movement(movement);
_something.save((err, saved) => {
err ? reject(err)
: resolve(saved);
});
});
}
const Movement = mongoose.model('Movement', movementSchema);
module.exports = Movement;
What I want to accomplish is to: save the movement collection with the _party as the full party document is at the moment of the save, I mean an embedded document of a copy of the Party document, which will not be affected by the updates done to the Party document in the future.
While I cannot change the Android Client, so I will still be getting only the ObjectId from it.
JSON example of what Android client will post: {"movementId":1, "relMovementId":4138, "_party":"58dbfe26194cfc5a9ec9b7c5"}
I'm confused now, and not sure if due to the way Android is posting the JSON, I need two schemas; one for the object received (i.e: with ObjectId and ref to Party) and a second one for the object persisted (i.e: with the schema referenced _party: Party.Schema) or if I could do something simpler as some populate prior to save... or what.
For the sake of closing this up:
I've implemented one of the approaches I had in mind while writing the question. Movement schema changed so that: _party: Party.Schema
When I get a POST to create a new movement I do a getById and use the result of that exec to populate the value as an embedded doc.
I am new to node.js coming from java experience. I have a situation that I am trying to wrap my head around. My stack is express.js, mongoose, ejs template. Here is my scenario:
I have a schema:
var UserSchema = mongoose.Schema({
name: {
type: String,
index: true
},
password: {
type: String,
select: false
},
email: {
type: String
},
academic: [{
qualification: String,
institute: String,
from: String,
to: String,
about: String
}]
});
there is a list of academics. I want to update only one academic object in that list. How would I go about this?
router.post('/academic/schools/update', function (req, res) {
});
I pass the values from ejs template into the route and getting the values in the req.body. How would I in node and mongoose query that specific object in the route and then updates its values. I have thought about maybe adding an Id to the academic object to be able to keep track of which to update.
Each academic sub document will have an _id after you save.
There are two ways you can do it. If you pass the id of the user and id of the academic sub-doc id in the url or request body, then you can update like this:
User.findById(userId).then(user => {
let academic = user.academic.id(academicId);
academic.qualification = 'something';
return user.save();
});
If you only pass the id of the academic sub-doc, then you can do it like this:
User.findOne({'academic._id': academicId}).then(user => {
let academic = user.academic.id(academicId);
academic.qualification = 'something';
return user.save();
});
Note that sub document array return from mongoose are mongoosearray instead of the native array data type. So you can manipulate them using .id .push .pop .remove method http://mongoosejs.com/docs/subdocs.html
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
})
?
Using mongoose populate:
http://mongoosejs.com/docs/populate.html
It seams that mongoose is forcing me to declare a ref value for populate when I first create the document but in my case i don't have the ref info yet. When I try to create a new document while providing an empty string I get to my developer field I get:
{"message":"Cast to ObjectId failed for value \"\" at path \"developer\"","name":"CastError","type":"ObjectId","value":"","path":"developer"}
Object that I'm saving through mongoose:
var Project = {
name: 'Coolproject',
status: 'pending',
developer: '',
type: 'basic',
};
Project.create(req.body, function(err, project) {
if(err) { return handleError(res, err); }
return
});
My Model:
var ProjectSchema = new Schema({
name: String,
status: {type:String, default:'pending'},
developer:{type: Schema.Types.ObjectId, ref: 'User'},
type:String
});
Basically I need to set it later, but it doesn't seam like this is possible. Currently my work around is populate it with a dummy user until later but this is less than desirable.
Thoughts?
Update
Realized that if i provide a object id like value (55132a418b3cde5546b01b37) it lets me save the document. Very odd. Guess it just figured it can find the document moves on. Wondering why this doesn't happen for a blank value.
The problem is explained in the error message. You cannot save an Empty String in the place of an ObjectId. The field is not listed as 'required', so there is no problem leaving it out and saving the document.
Code correction:
// you can save this
var Project = {
name: 'Coolproject',
status: 'pending',
type: 'basic',
};
You need to use the sparse index in model.
So, the valid model can have developer equal to nil
var ProjectSchema = new Schema({
name: String,
status: {type:String, default:'pending'},
developer:{type: Schema.Types.ObjectId, ref: 'User', sparse:true},
type:String
});
See
http://mongoosejs.com/docs/api.html#schematype_SchemaType-sparse and
http://docs.mongodb.org/manual/core/index-sparse/
for additional info
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