Modifying array directly in mongoose doesn´t works - node.js

I have a mongoose schema with a field array.
I need to set the array field directly without get rid off the existent values.
I am using
item.files.concat(myfiles);
in the next code, but it doesn´t work. Only the last item from array is saved
//code
var files=['file1','file2','file3']
var myfiles=[]
files.forEach(function(file){
myfiles.push({title:file});
}
});
//router
FileSchema.findById(id).exec( (err,item) => {
//fill files -- error is here
item.files.concat(myfiles);
item.save(function (err, data) {
if (err) {
return res.status(500).send(err)
}
res.send(data); //send response
})
})
//schema
const mongoose=require('mongoose');
const fileSchema = new mongoose.Schema({
id: {type: Number, unique:true},
...
...
files:[{title:{type: String}}]
});

From MDN web docs:
The concat() method is used to merge two or more arrays. This method does not change the existing arrays, but instead returns a new array.
item.files = item.files.concat(myfiles);

Related

How to check if object fields are empty before inserting into mongooseDB from CSV file - Nodejs

My code is working correctly, I wanted to add some error checking to make sure no object with empty fields could be inserted into the DB. I am having some trouble getting that part working right. The data is coming from a local csv file like so:
TempCSV.csv
name, address, ID, contactInfo
bob, 214 elm, 123, email
joe, 817 beach, 321, email
,45 hollywood,456,
The first two lines should be inserted but not the last one because it is missing name, which is a required field. So basically each field needs to be checked while it is parsed to see if it is empty or not.
Here is my code
dbparser.js
const mongoose = require("mongoose");
const passport = require("passport");
const csvtojson = require("csvtojson");
const router = require("express").Router();
const userSchema= new mongoose.Schema({
name: String,
address: String,
ID: String,
contactInfo: String
});
const userModel= mongoose.model("User");
const async = require('async');
csvtojson()
.fromFile("TempCSV.csv")
.then(csvData => {
async.eachSeries(csvData,(data,callback) => {
let entity = {
name: data.name,
address: data.owner,
ID: data.ownerID,
};
userModel.create(entity, function(err)
{
if(err) return callback(err);
return callback(null);
})
},
(err) => {
if(err) console.log(err);
console.log("users are successfully imported!!!");
});
});
I thought adding the const userSchema would prevent it from adding empty fields but it did not.
Any help would be appreciated.
You can another function which checks the emptiness of the object passed. It will return true if obj has all keys have a truthy value, returns false otherwise.
function hasKeys(obj, keys) {
return keys.every(key => !!obj[key])
}
And then check the object before inserting into the database.

How to add object to nested array in mongoose?

In essence what I am trying to do is something along the lines of FindByIdAndCreate, a method which does not exist in mongoose.
I have a schema as so:
const WordSchema = new Schema ({
TargetWord: String,
Translation: String,
ExampleSentences: [{
Number: Number, //increment somehow each time
Sentence: String,
}],
});
I have a form where the user can add example sentences of this target word, the route for which looks like this:
router.put("/word/:id/add", async(req, res) => {
//get the new sentence from the field
var NewSentence = req.body.Sentence;
Now once I have this new sentence saved to the variable NewSentence I want to create a new object within the WordSchema.ExampleSentences array which contains the new sentences itself, and the number which should automatically increment.
I have fiddled around with FindByIdAndUpdate to no avail,this syntax does not work because it throws an error at the use of .
WordSchema.findByIdAndUpdate(req.params.id, {ExampleSentences.Sentence: NewSentence}, ...
The only solution to increment your counter is to retrieve every document with a good old 'find' and create your new entry accordingly, since 'update' has no way to self reference a document during the process.
router.put("/word/:id/add", async(req, res) => {
WordSchema.find({_id: req.body.id}, function(results) {
if (results.length === 0) return res.json();
const word = result[0];
const NewExampleSentence = {
Number: word.ExampleSentences.length, // if your counter start from 1, then add 1
Sentence: req.body.Sentence
};
word.ExampleSentences.push(NewExampleSentence);
word.save(function(err) {
if (err) {
// handle error
}
return res.json();
})
}
})

Create subsubdocs in Mongoose with Nested Schema

I have a User Schema that includes nested Schemas - overall three layers deep. I have no problem adding to Polls, but cannot add Inputs for the life of me. I have tried to get a user then handle the data like regular json (such as getting the index of the polls array and then pushing to the nested input array). I have tried creating a new input and pushing it to the inputs array.
The following code works perfectly for creating a new poll on a given user:
const poll = user.polls.create({});
user.polls.push(poll);
user.save(function(err, data) {
//more stuff here
})
However, when I try the same method on polls, I cannot save to the DB.
const poll = user.polls.id(<some _id>);
const input = poll.create({}); // or user.polls.id(<some _id>).create({});
poll.inputs.push(input);
I have gone back and forth and tried several things and cannot seem to get this to work given my current data structure. I have read several posts on SO as well as other online sources but most give examples for the first embedded subdoc, not the subdoc of the subdoc. Any help you can provide would be greatly appreciated. Thanks. I'm thinking of scrapping Mongoose on this project and just using the mongodb package for clearer control over what is going on with my data.
Here's my current code:
From User.js
const InputSchema = new Schema({
[ALL SORTS OF VARIOUS DATA HERE]
});
const PollSchema = new Schema({
inputs: [InputSchema],
});
const UserSchema = new Schema({
polls: [PollSchema]
});
module.exports = mongoose.model('User', UserSchema);
From controllers.js
const User = require('../models/User');
router.post('/polls/inputs/add/:creatorId', function(req, res) {
let title = req.body.option.title,
order = req.body.option.order,
voters= [];
User.findOne({ creatorId: req.params.creatorId })
.then(user => {
const input = {order, title, voters};
user.polls.id(req.body.pollId).inputs.push(input);
user.save(function(err, data) {
if (err) {
res.statusCode = 500;
return res.json({ title: 'Save Error', message: err });
}
console.log('Success!');
res.json({ input: input });
});
}).catch(err => {
res.statusCode = 500;
return res.json({ title: 'DB Error', message: err });
});;
});
Mongoose isn't able to track changes in subobjects like this, so the field must be marked explicitely as needing to be updated like so:
user.markModified('polls')

Result of mongoose.save is incorrect when adding item to mixed schema array

Following is a function that shows the issue:
var mongoose = require('mongoose');
var connection = mongoose.createConnection('mongodb://localhost:27017');
connection.once('open', function () {
var schema = new mongoose.Schema({
obj: [{}] //mongoose.Schema.Types.Mixed
});
var Model = connection.model('mtest', schema);
var model = new Model({
obj: [{ name: 'Original' }]
});
model.save(function (err, res) {
console.log('result 1', res);
Model.findOne({_id: res._id}, function (err, res) {
res.obj[0].name = 'Modified';
res.obj.push({ name: 'other' });
//res.markModified('obj'); // using markModified does not help
res.save(function (err, res) {
console.log('result 2', res);
connection.close();
process.exit();
});
});
})
});
The output of "result 2" shows "Modified" for the first item in "obj": obj: [ { name: 'Modified' }, { name: 'other' } ].
However, in the database the value of the first item is still "Original".
This only happens when pushing a second item into the array (otherwise the first item is indeed modified).
Adding markModified does resolve the issue.
I'm using an array of empty objects types in the schema because in reality this use case deals with with schemas that inherit from each other, so no single schema can be used here.
Is it a bug? The only workaround I've found is to clear the array and add all the items again. I'd like to know if there's a better solution.
You could either alter your markModified call to identify the index of the element you changed "outside" of the array access methods:
res.obj[0].name = 'Modified';
res.obj.push({ name: 'other' });
res.markModified('obj.0');
Or switch to using the set array access method to alert name (which looks pretty goofy, but does work):
res.obj[0].name = 'Modified';
res.obj.set(0, res.obj[0]);
res.obj.push({ name: 'other' });

Incorrect Subdocument Being Updated?

I've got a Schema with an array of subdocuments, I need to update just one of them. I do a findOne with the ID of the subdocument then cut down the response to just that subdocument at position 0 in the returned array.
No matter what I do, I can only get the first subdocument in the parent document to update, even when it should be the 2nd, 3rd, etc. Only the first gets updated no matter what. As far as I can tell it should be working, but I'm not a MongoDB or Mongoose expert, so I'm obviously wrong somewhere.
var template = req.params.template;
var page = req.params.page;
console.log('Template ID: ' + template);
db.Template.findOne({'pages._id': page}, {'pages.$': 1}, function (err, tmpl) {
console.log('Matched Template ID: ' + tmpl._id);
var pagePath = tmpl.pages[0].body;
if(req.body.file) {
tmpl.pages[0].background = req.body.filename;
tmpl.save(function (err, updTmpl) {
console.log(updTmpl);
if (err) console.log(err);
});
// db.Template.findOne(tmpl._id, function (err, tpl) {
// console.log('Additional Matched ID: ' + tmpl._id);
// console.log(tpl);
// tpl.pages[tmpl.pages[0].number].background = req.body.filename;
// tpl.save(function (err, updTmpl){
// if (err) console.log(err);
// });
// });
}
In the console, all of the ID's match up properly, and even when I return the updTmpl, it's saying that it's updated the proper record, even though its actually updated the first subdocument and not the one it's saying it has.
The schema just in case:
var envelopeSchema = new Schema({
background: String,
body: String
});
var pageSchema = new Schema({
background: String,
number: Number,
body: String
});
var templateSchema = new Schema({
name: { type: String, required: true, unique: true },
envelope: [envelopeSchema],
pagecount: Number,
pages: [pageSchema]
});
templateSchema.plugin(timestamps);
module.exports = mongoose.model("Template", templateSchema);
First, if you need req.body.file to be set in order for the update to execute I would recommend checking that before you run the query.
Also, is that a typo and req.body.file is supposed to be req.body.filename? I will assume it is for the example.
Additionally, and I have not done serious testing on this, but I believe your call will be more efficient if you specify your Template._id:
var template_id = req.params.template,
page_id = req.params.page;
if(req.body.filename){
db.Template.update({_id: template_id, 'pages._id': page_id},
{ $set: {'pages.$.background': req.body.filename} },
function(err, res){
if(err){
// err
} else {
// success
}
});
} else {
// return error / missing data
}
Mongoose doesn't understand documents returned with the positional projection operator. It always updates an array of subdocuments positionally, not by id. You may be interested in looking at the actual queries that mongoose is building - use mongoose.set('debug', true).
You'll have to either get the entire array, or build your own MongoDB query and go around mongoose. I would suggest the former; if pulling the entire array is going to cause performance issues, you're probably better off making each of the subdocuments a top-level document - documents that grow without bounds become problematic (at the very least because Mongo has a hard document size limit).
I'm not familiar with mongoose but the Mongo update query might be:
db.Template.update( { "pages._id": page }, { $set: { "pages.$.body" : body } } )

Resources