Mongo: save an array of objects - javascript

I'm trying to save an array of objects to my mongo db in one save. I'd like to store each new object into a temporary array first, then send it to the server.js via an ajax post request. But each time it gets to the server, the data becomes just one giant object, and not an array of objects. Is what I'm trying to do possible with Mongo, or am I missing something? Here's my code below.
SCHEMA
var capsuleSchema = new mongoose.Schema({
qa: [{
type: mongoose.Schema.Types.ObjectId, ref: 'Question',
question: String,
answer: String
}],
user: { type: mongoose.Schema.Types.ObjectId, ref: 'User'},
date_of_return: Date,
created_at: { type: Date, default: Date.now }
});
var Capsule = mongoose.model('Capsule', capsuleSchema);
module.exports = Capsule;
APP.JS
var temp_id = id;
var temp_q = quest;
var temp_ans = $('#response').val();
var tempQA = {
id: temp_id,
question: temp_q,
answer: temp_ans
};
saveQuestion(tempQA);
var saveQuestion = function(tempQA) {
answeredQuestions.push(tempQA);
}
var capsuleData = {
questions: answeredQuestions
}
newCapsule(capsuleData);
var newCapsule = function(capsuleData) {
$.ajax({
url: "http://localhost:3000/capsules",
method: "POST",
dataType: 'json',
data: capsuleData
}).done(function(data){
// returns "capsule creation complete"
});
}; // end newCapsule
SERVER.JS
app.post('/capsules', function(req, res) {
var qa_array = [];
var capsule = new Capsule({
qa: req.body.questions,
user: req.cookies.loggedinId,
});
capsule.qa.push(req.body)
capsule.save( function(err, capsule) {
if (err) {
console.log(err);
res.statusCode = 503;
} else {
res.send(capsule);
}; // end if/else
}); // end save
}); // end post time-capsule
UPDATE:
I accidentally put the newCapsule(capsuleData) in the capsule object.--fixed.
I think the problem is in the ajax request. When the Array of objects goes through and gets to the server.js it gets reformatted into and array of one object containing array-innumerated, key:value pairs as strings even before I do anything with it on the server side. ("ie: 'questions[0][id]' : '12345', 'questions[0][question]' : 'how are you?'" etc.
I need it to stay as an array of objects though.

If you want to save multiple documents, try db.collection.insert
Check it out

The below code is not syntactically correct. capsuleData isn't a function its an object literal.
var capsuleData = { questions: answeredQuestions newCapsule(capsuleData); }
The below code attaches a method to the object. You also need to include Capsule() as a javascript function that is accessible so new Capsule() can create the javascript object. But that is still creating an object and not an array. You need to create the array client side and then pass it to the server to be processed.
var capsuleData = { questions: answeredQuestions,
anObjectMethod : new Capsule(capsuleData); }
Review this for more in-depth help https://developer.mozilla.org/en-US/docs/Web/JavaScript/A_re-introduction_to_JavaScript

Related

Mongoose : Cast to ObjectId failed for value "Some String" at path "_id"

New to MongoDB, Javascript stack and need help understanding cause of this error.
I have my model created :
const
Mongoose = require('mongoose');
Schema = Mongoose.Schema,
Model = Mongoose.model;
module.exports = Model('Project',
new Schema({
icon : String,
name : String,
state : String,
number : String
})
);
This is my MongoDB document :
[![MongoDB Document][1]][1]
I am attempting to receive all the documents in the collection when I call the API so therefore as per the Mongoose document I am using the find() method.
Here is my API Implementation:
const Project = require('../../models/project');
router.get('/projects/:page?/:limit?',
function(req, res, next){
const page = Math.max(req.params.page || 1, 1) - 1;
const limit = Math.max(req.params.limit || 20, 20);
//Verified : I am hitting the API
console.log("Reached API /projects");
Project.find()
.populate('icon')
.populate('name')
.populate('state')
.populate('number')
.limit(limit).skip(page * limit).exec(
function(err, project)
{
if (err) { return next(err); }
res.send(project);
}
); //End of exec()
}//End of unction
);
I am successful in making the API call using fetch() but I am receiving "Cast to ObjectId failed error" for all the String values.
I believe there is something really simple within my Mongo DB document that I might be missing. Please help me understand and solve this issue.
**EDIT ---
The error seems to point at the string values of the keys:
**
Thank you
Population is the process of automatically replacing the specified paths in the document with document(s) from other collection(s). So you're Id cast is not valid, because of string, you need to have ObjectId, some changes need to be made before it, Let's debug:
const alldata = await Project.find()
console.log(alldata) // ?
does this return something, I'm using async await here if it return data then the problem is with your populate because your Id case isn't valid as you save in schema string and you're referring here populate, example of using populate:
module.exports = Model('Project',
new Schema({
icon : [{ type: Schema.ObjectId, ref: 'your icon document' }],
name : [{ type: Schema.ObjectId, ref: 'you name document' }],
state : [{ type: Schema.ObjectId, ref: 'state document' }],
number : [{ type: Schema.ObjectId, ref: 'number document' }]
})
);
but it seems to me that you don't need to use the populate because you have simple data, name, number... so you should be good to go with the above example
Resources: mongoose fetching data, using populate, relation

Adding to an array in MongoDB using $addToSet

I'm trying to add data to an array defined in my mongoDB called "signedUp" it is within my Timetable Schema. So far i've been able to update other fields of my schema correctly however my signedUp array always remains empty. I ensured the variable being added was not empty.
Here is my Schema
var TimetableSchema = new mongoose.Schema({
date: {
type: String,
required: true,
trim: true
},
spaces: {
type: Number,
required: true
},
classes: [ClassSchema],
signedUp: [{
type: String
}]
});
This was my latest attempt but no value is ever added to the signedUp array.
My API update request
id = {_id: req.params.id};
space = {spaces: newRemainingCapacity};
signedUp = {$addToSet:{signedUp: currentUser}};
Timetable.update(id,space,signedUp,function(err, timetable){
if(err) throw err;
console.log("updates");
res.send({timetable});
});
Thanks
You can take a look at db.collection.update() documentation. Second parameter takes update and 3rd one represents operation options while you're trying to pass your $addToSet as third param. Your operation should look like below:
id = {_id: req.params.id};
space = { $set: { spaces: newRemainingCapacity }};
signedUp = { $addToSet:{ signedUp: currentUser}};
update = { ...space, ...signedUp }
Timetable.update(id,update,function(err, timetable){
if(err) throw err;
console.log("updates");
res.send({timetable});
});
space and signedUp are together the second argument.
try this:
id = {_id: req.params.id};
space = {spaces: newRemainingCapacity};
signedUp = {$addToSet:{signedUp: currentUser}};
Timetable.update(id, {...space, ...signedUp}, function(err, timetable){
if(err) throw err;
console.log("updates");
res.send({timetable});
});

Save object to array in another model

So I got two mongoose-models:
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var eventSchema = new mongoose.Schema({
name: String,
date: String,
dogs: [{ type: Schema.Types.ObjectId, ref: 'Dog' }]
});
module.exports = mongoose.model('Event', eventSchema);
and
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var dogSchema = new mongoose.Schema({
name: String,
age: String,
gender: String,
});
module.exports = mongoose.model('Dog', dogSchema);
Event contains an array of dogs and im trying to figure out how to add/delete dogs to this array.
On the client I got this method:
$.ajax({
url: "http://localhost:3000/api/events/",
dataType: 'json',
type: 'POST', // Not sure if I should Post or Put...
data: {event_Id : this.props.choosenEvent._id, //Here I got the Id of the Event that i want to update by
dog_Id : this.props.events[dog]._id }, //adding this dog, which Id is here
success: function(data) {
}.bind(this),
});
},
On the server, NodeJs, I got my routes to the API. To me, it makes sense to use an PUT-method and start by getting the right Event with the event_Id passed as a param. Something like:
router.route('/events/:event_id')
.put(function(req, res) {
Event
.findById({ _id: req.param.event_id })
.populate('dogs')
});
But Im stuck at this point. Any help appreciated. Thanks!
Update!
Thank you! Your code helped a lot, you used lodash .remove to delete a dog from the array, is there a similar way to add an item with lodash?
I gave the add method a go like this:
router.route('/events')
.post(function(req, res) {
// Your data is inside req.body
Event
.findById({ _id: req.body.event_Id })
// execute the query
.exec(function(err, eventData) {
// Do some error handing
// Your dogs are inside eventData.dogs
eventData.dogs.push(req.body.dog_Id);
console.log(eventData)
});
// Update your eventDate here
Event.update({_id: req.body.event_id}, eventData)
.exec(function(err, update) {
// Do some error handing
// And send your response
});
});
When I hit the console.log(eventData) I can see that dog_id gets added to the array as it should. However it does not get saved to the db and the error says that eventData is not defined in Event.Update. I suspect this is a Js-scope-issue.
Onte thing that boggles me is this:
Obviously I would like to be able to add and remove dogs from the array and the
route is this: router.route('/events') .
But if both the add-method and the remove-method is on the same route, how can the code know which one I am going for?
There are a few mistakes you are making. First of all, you are making a POST request but your route accepts a PUT request. I have updated your code so it accepts a POST.
When posting objects, your data is inside req.body. req.params is used for url parameters. This is also the case when using a PUT request.
Populating dogs is not really necessary. You are sending your dog_id to your function so you can delete your item from your array which removes your dog from your event. This should do the trick. Please note that this does not remove your dog from your DB but only from your event.
Last but not least. I am using lodash. _.remove is a lodash function. You should definitely check it out, it will help you a lot.
Take a look at my code. It should get you going:
router.route('/events/:event_id')
// Since you are posting, you should use POST from JavaScript instead of PUT
.post(function(req, res) {
// Your data is inside req.body
Event
.findById({ _id: req.body.event_id })
// execute the query
.exec(function(err, eventData) {
// Do some error handing
// Your dogs are inside eventData.dogs
_.remove(eventData.dogs, function(d) {
return d._id === req.body.dog_Id;
});
// Update your eventDate here
Event.update({_id: req.body.event_id}, eventData)
.exec(function(err, update) {
// Do some error handing
// And send your response
});
});
});
UPDATE:
I do not think there is a way to add items to an array with lodash but you can simply use push like you did in your code example. That works just fine.
Your update is not working because your are executing the findById and update at the same time. You will have to find the item first, add the id and THEN update the item :) Move your update function inside the callback of your findById function and that should be fixed. So it looks like this:
router.route('/events')
.post(function(req, res) {
// Your data is inside req.body
Event
.findById({ _id: req.body.event_Id })
// execute the query
.exec(function(err, eventData) {
// Do some error handing
// Your dogs are inside eventData.dogs
eventData.dogs.push(req.body.dog_Id);
console.log(eventData)
// Update your eventDate here
Event.update({_id: req.body.event_id}, eventData)
.exec(function(err, update) {
// Do some error handing
// And send your response
});
});
});
You can add different functions on the same route as long as the method is different from the others. Take a look at REST at this answer. You can have a GET, POST, PUT & DELETE on /events. This is defined by this rule:
router.route('/events').post();

Mongoose seems to fail quietly

So have a completely separate issue (I think) which is saving an array to a mongo document. So I took out that part, created a data structure that would have the same issue to try to resolve it. But now my test code seems to finish before the save function returns, just not certain how to resolve either issue, or if they potentially are connected.
The mongoose schema:
var mongoose = require('mongoose');
ObjectId = mongoose.Schema.Types.ObjectId;
var offerSchema = mongoose.Schema({
closeDate: Date,
settleDate: Date,
schemaVersion: Number,
_offered: [{ type: ObjectId, ref: 'User'}], //Ids of thos offered
_offerDate: { type: Date },// The date they where offered
_accepted: [{ type: ObjectId, ref: 'User'}],//Ids of those accepted
_acceptedDate: [{ type: Date }], //Dates when accepted
});
// create the model for users and expose it to our app
module.exports = mongoose.model('offer', offerSchema);
So I have written the code below to recreate the issue, the below code should work but not store a value in _offered.
var offer = require('../models/offerSchema.js')
var indata = {"offer":{"closeDate":"2015-08-31T13:26:36.512Z","settleDate":"2015-08-31T13:26:36.512Z","type":1,"_offered":[{"id":"55dc7994ed0fcf4a58d4a689"},{"id":"55dcd30915e3be545a51bebd"}],"_offerDate":"2015-08-31T13:26:36.512Z"}}
var thisOffer = indata.offer
for ( var i in thisOffer ){
console.log("Got "+ i +" is " + thisOffer[i])
}
var myOffer = new offer(thisOffer);
myOffer._offered = undefined
var promise =
myOffer.save(function(err){
if(err){
console.log('Got an error: ' + err)
}
console.log('Got an id: ' + myOffer._id)
return 0;
}).then(function() {
console.log("I get here and quit?");
})
However, the code seems to complete before the save have completed, and I am actually not certain how to avoid this.
You are mixing logic with callbacks and promises. So you do not need the callback, just act on the promise returned:
myOffer._offered = undefined
myOffer.save()
.then(function() {
console.log("I get here and quit?");
})
.then(null,function(err) {
console.log(err);
});
I noticed you made another attempt using Bluebird promises, but it is not necessary as if you implement as shown here any errors will be appropriately routed.
Threre are notes that mongooose 5.x is slated to have a more standardised approach to promises and/or directly use Bluebird promises when configured
OK, found the issue. mongoose save is not promisified (or at least that is what I recon the issue is), changed the first part to use bluebird for promises, then changed the save to use saveAsync as below
Schema (and connection, and bluebird)
var mongoose = require('mongoose');
var Promise = require('bluebird');
Promise.promisifyAll(mongoose);
mongoose.connect('mongodb://webbetcha:M0t0rWrl3d#localhost/betchadb');
ObjectId = mongoose.Schema.Types.ObjectId;
var offerSchema = mongoose.Schema({
closeDate: Date,
settleDate: Date,
schemaVersion: Number,
_offered: [{ type: ObjectId, ref: 'User'}], //Ids of thos offered
_offerDate: { type: Date },// The date they where offered
_accepted: [{ type: ObjectId, ref: 'User'}],//Ids of those accepted
_acceptedDate: [{ type: Date }], //Dates when accepted
});
// create the model for users and expose it to our app
module.exports = mongoose.model('offer', offerSchema);
And the code
var offer = require('../models/offerSchema.js');
var indata = {"offer":{"closeDate":"2015-08-31T13:26:36.512Z","settleDate":"2015-08-31T13:26:36.512Z","type":1,"_offered":[{"id":"55dc7994ed0fcf4a58d4a689"},{"id":"55dcd30915e3be545a51bebd"}],"_offerDate":"2015-08-31T13:26:36.512Z"}}
var thisOffer = indata.offer
for ( var i in thisOffer ){
console.log("Got "+ i +" is " + thisOffer[i])
}
var myOffer = new offer(thisOffer);
myOffer._offered = undefined
myOffer.saveAsync()
.then(function(doc){
console.log('Got an id: ' + myOffer._id)
})
.catch(function(err){
if(err){
console.log('Got an error: ' + err)
}
});

Why can't I modify my model's virtual attribute in my controller?

I have the following virtual attribute defined in my model.
var ObjectSchema = new Schema({
short_id: {
type: String,
required: true,
unique: true
},
created: {
type: Date,
default: Date.now
}
})
ObjectSchema.virtual('voted').get(function () {
return false;
})
I try to change it in my controller, yet it still remains unchanged, why is this?
Object.find({ short_id: req.params.shortId }).exec(function(err, objects) {
objects[0].voted = true;
res.jsonp(objects[0]);
});
The JSON still says object.voted = false
When you want to freely manipulate the results of a Mongoose query, you can use a lean() query which generates plain JavaScript objects instead of model instances:
Object.find({short_id: req.params.shortId}).lean().exec(function(err, objects) {
objects[0].voted = true;
res.jsonp(objects[0]);
});
You should convert all your Mongoose objects to plain JS objects with .toObject() before doing so:
Object.findOne({ short_id: req.params.shortId }).exec(function(err, doc) {
object = doc.toObject();
object.voted = true;
res.jsonp(object);
});
Or, if you don't need setters, getters, virtuals or any other of Mongoose magic to be applied to your documents, you may use lean queries (as JohnnyHK suggested):
Object.findOne({ short_id: req.params.shortId }).lean().exec(function(err, object) {
object.voted = true;
res.jsonp(object);
});
In this case you'll see all your documents exactly like they are stored in MongoDB. So, there will be no voted field despite your virtaul. There will be no .save() method either.

Categories