Mongoose - modifiedpaths not identifying array field as modified - javascript

I have the following schema:
var Person = new Schema({
persondetails: {
name: {
type: String,
uppercase: true,
required: true
},
nationalities: [ {
nationality : String
}]
});
and a presave function to check modified paths:
schema
.pre('save', function(next) {
var document = this;
_.each(document.modifiedPaths(), function(path) {
console.log(path);
});
return next();
});
if i do:
new Person({persondetails: {
name: 'Some Name',
nationalities: [ {nationality: 'Some Country'}]
}
)
Or even pick an already existing 'Person' and add a new element to its nationalities array,
presaved hook prints:
persondetails
persondetails.name
persondetails.nationalities
But i was expecting the 'persondetails.nationalities.0.nationality' as well.
Is this expected behavior or am i missing something ?

Related

How to validate Array of Objects in Mongoose

I need to validate the array of objects in my schema
Schema:
user: [{
name: String,
Age: String,
Contact: Number
}]
How to validate name, age and contact.
I assume your user array is inside another schema.
Let's say we have a Course model with users like this:
const mongoose = require("mongoose");
const courseSchema = new mongoose.Schema({
name: {
type: String,
required: true
},
users: [
{
name: { type: String, required: [true, "Name is required"] },
age: { type: Number, required: [true, "Age is required"] },
contact: { type: Number, required: [true, "Contact is required"] }
}
]
});
const Course = mongoose.model("Post", courseSchema);
module.exports = Course;
To validate this in a post route you can use mongoose model validateSync method:
const Course = require("../models/course");
router.post("/course", async (req, res) => {
const { name, users } = req.body;
const course = new Course({ name, users });
const validationErrors = course.validateSync();
if (validationErrors) {
res.status(400).send(validationErrors.message);
} else {
const result = await course.save();
res.send(result);
}
});
When we send a requset body without required fields like age and contact:
(you can also transform validationErrors.errors for more useful error messages.)
{
"name": "course 1",
"users": [{"name": "name 1"}, {"name": "name 2", "age": 22, "contact": 2222}]
}
The result will be like this:
Post validation failed: users.0.contact: Contact is required, users.0.age: Age is required
It will be similar to the usual validation but inside an array, you need to make a validator function as so:
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
//the custom validation that will get applied to the features attribute.
var notEmpty = function(features){
if(features.length === 0){return false}
else {return true};
}
var CarSchema = new Schema({
name: {
type: String,
required: true,
},
features: [{
type: Schema.ObjectId,
ref: Feature
required: true; //this will prevent a Car model from being saved without a features array.
validate: [notEmpty, 'Please add at least one feature in the features array'] //this adds custom validation through the function check of notEmpty.
}]
});
var FeatureSchema = new Schema({
name: {
type: String,
required: true //this will prevent a Feature model from being saved without a name attribute.
}
});
mongoose.model('Car', CarSchema);
mongoose.model('Feature', FeatureSchema);
By using type key/property:
var breakfastSchema = new Schema({
eggs: {
type: Number,
min: [6, 'Too few eggs'],
max: 12
},
bacon: {
type: Number,
required: [true, 'Why no bacon?']
},
drink: {
type: String,
enum: ['Coffee', 'Tea'],
required: function() {
return this.bacon > 3;
}
}
});

An empty array is being returned with Mongoose, Node.js

I am writing code with node.js. Quite new to this and the problem is that mongoose returns an empty array. There must be a mistake somewhere in this code, but I cannot find it. Any ideas?
Dresses schema
var dressesSchema = mongoose.Schema({
title:{
type: String,
required: true
},
description:{
type: String,
required: true
}
});
var Dress = module.exports = mongoose.model('Dress', dressesSchema);
Get dresses from database
module.exports.getDresses = function(callback, limit){
Dress.find(callback).limit(limit);
};
Dress = require('./models/dress');
app.get('/api/dresses', function(req, res){
Dress.getDresses(function(err, dresses){
if(err){
throw err;
}
res.json(dresses);
});
});
example how to use find via mongoose :
// named john and at least 18
MyModel.find({ name: 'john', age: { $gte: 18 }});
// executes immediately, passing results to callback
MyModel.find({ name: 'john', age: { $gte: 18 }}, function (err, docs) {});
// name LIKE john and only selecting the "name" and "friends" fields, executing immediately
MyModel.find({ name: /john/i }, 'name friends', function (err, docs) { })
// passing options
MyModel.find({ name: /john/i }, null, { skip: 10 })
// passing options and executing immediately
MyModel.find({ name: /john/i }, null, { skip: 10 }, function (err, docs) {});
// executing a query explicitly
var query = MyModel.find({ name: /john/i }, null, { skip: 10 })
query.exec(function (err, docs) {});
// using the promise returned from executing a query
var query = MyModel.find({ name: /john/i }, null, { skip: 10 });
var promise = query.exec();
promise.addBack(function (err, docs) {});
taken from link
try this:
Dresses schema :
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
const Dress= new Schema({
title:{
type: String,
required: true
},
description:{
type: String,
required: true
},
price:{
type: String
},
stock:{
type: String
},
location:{
country:{
type: String
},
city:{
type: String
},
street:{
type: String
},
lon:{
type: String
},
lat:{
type: String
}
}
});
module.exports = mongoose.model("dress", Dress);
Get dresses from database:
const {Dress} = require('./models/dress');
Dress.find().then(result => {
console.log(result);
});

Creating a mongoose model containing array types return undefined for those in my docker container

I have a mongoose model containing 2 properties Array of String and some others:
var Model = new Schema({
_id: {type: Number, required: true, unique: true},
name: {type: String, required: true},
oneList: [String],
anotherList: [String]
});
I create the model:
var model = new Model({
_id: 1,
name: 'model',
oneList: ['a', 'b'],
anotherList: ['c', 'd']
})
but when I inspect the model all the list are undefined:
model._doc === {
_id: 1,
name: 'model',
oneList: undefined,
anotherList: undefined
}
I tried some variations:
change the model definition from [String] to [ ]
create separately the data outside the model then pass it
create the model without list data then add it to the model
Even when I create an empty model:
var model = new Model({})
model._doc.oneList === undefined
model._doc.anotherList === undefined
Context:
The problem occurs on a docker container but not on my local machine
node: v4.4.7
mongoose: v4.6.0
GitHub
I had the same issue, apparently when you have a nested array within your model, mongoose has an open issue 1335 that saves an empty array when a property references a schema. I experimented with presaves to force the property to be an empty array if the property's length is 0 or undefined.
Also be careful when specifying unique=true in the property's schema, as empty or undefined properties will violate the indexing and throw an error.
Note:
var mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/test');
var barSchema = new mongoose.Schema({
baz: String
});
var fooSchema = new mongoose.Schema({
bars: [barSchema]
});
var Foo = mongoose.model('Foo', fooSchema);
var foo = new Foo();
console.log(foo); // { _id: 55256e20e3c38434687034fb, bars: [] }
foo.save(function(err, foo2) {
console.log(foo2); // { __v: 0, _id: 55256e20e3c38434687034fb, bars: [] }
foo2.bars = undefined;
foo2.save(function(err, foo3) {
console.log(foo3); // { __v: 0, _id: 55256e20e3c38434687034fb, bars: undefined }
Foo.findOne({ _id: foo3._id }, function(err, foo4) {
console.log(foo4); // { _id: 55256e20e3c38434687034fb, __v: 0, bars: [] }
mongoose.disconnect();
});
});
});
You can do like this:
var schema = new Schema({
_id: {
type: Number,
required: true,
unique: true
},
name: {
type: String,
required: true
},
oneList: [String],
anotherList: [String]
}),
Model = mongoose.model('Model', schema),
m = new Model();
m._id = 1;
m.name = 'model';
m.oneList.push('a', 'b');
m.anotherList.push('c', 'd');

Mongodb: Can't append to array using string field name

i am trying to push inside a subarray using $push but got a Mongo error, and not able to get through this after considerable search on google, and findOneAndUpdate didn't worked out so i used find and update separately
{ [MongoError: can't append to array using string field name: to]
name: 'MongoError',
err: 'can\'t append to array using string field name: to',
code: 13048,
n: 0,
lastOp: { _bsontype: 'Timestamp', low_: 2, high_: 1418993115 },
Schema:
var NetworkSchema = new Schema({
UserID: {
type: Schema.Types.ObjectId,
ref: 'User'
},
NetworkList: [{
type: Schema.Types.ObjectId,
ref: 'User'
}],
NetworkRequest: [{
from: [{
type:Schema.Types.ObjectId,
ref: 'User'
}],
to: [{
type: Schema.Types.ObjectId,
ref: 'User'
}]
}]
});
Document:
{
"UserID" : ObjectId("549416c9cbe0e42c1adb42b5"),
"_id" : ObjectId("549416c9cbe0e42c1adb42b6"),
"NetworkRequest" : [
{
"from" : [],
"to" : []
}
],
"NetworkList" : [],
"__v" : 0
}
Controller:
exports.update = function(req,res) {
var network = req.network;
var query={'UserID':req.body.UserID};
var update = {$push:{'NetworkRequest.to': req.body.FriendID}};
Network.find(query,function(err){
if (err) {
console.log(err);
return err;
} else {
}
});
Network.update(query,update,{upsert:true},function(err,user){
console.log(user);
if (err) {
console.log(err);
return err;
} else {
console.log('User'+user);
}
});
};
Everything #cbass said in his answer is correct, but since you don't have a unique identifier in your NetworkRequest element to target, you need to do it by position:
var query = {'UserID': req.body.UserID};
var update = {$push:{'NetworkRequest.0.to': req.body.FriendID}};
Test.update(query, update, {upsert: true}, function(err, result) { ... });
'NetworkRequest.0.to' identifies the to field of the first element of the NetworkRequest array.
Your query var query={'UserID':req.body.UserID}; identifies the document you want to edit. Then you need another query to identify which object in the NetworkRequest array that the UserID should be pushed into. Something like below:
var query = {
'UserID':req.body.UserID,
'NetworkRequest._id': ObjectId(someNetworkRequestId)
};
Then use this update query containing $ which is the index of the object in the nested array(NetworkRequest)
var update = {
$push:{
'NetworkRequest.$.to': req.body.FriendID
}
};

Mongoose, sort query by populated field

As far as I know, it's possible to sort populated docs with Mongoose (source).
I'm searching for a way to sort a query by one or more populated fields.
Consider this two Mongoose schemas :
var Wizard = new Schema({
name : { type: String }
, spells : { [{ type: Schema.ObjectId, ref: 'Spell' }] }
});
var Spell = new Schema({
name : { type: String }
, damages : { type: Number }
});
Sample JSON:
[{
name: 'Gandalf',
spells: [{
name: 'Fireball',
damages: 20
}]
}, {
name: 'Saruman',
spells: [{
name: 'Frozenball',
damages: 10
}]
}, {
name: 'Radagast',
spells: [{
name: 'Lightball',
damages: 15
}]
}]
I would like to sort those wizards by their spell damages, using something like :
WizardModel
.find({})
.populate('spells', myfields, myconditions, { sort: [['damages', 'asc']] })
// Should return in the right order: Saruman, Radagast, Gandalf
I'm actually doing those sorts by hands after querying and would like to optimize that.
You can explicitly specify only required parameters of populate method:
WizardModel
.find({})
.populate({path: 'spells', options: { sort: [['damages', 'asc']] }})
Have a look at http://mongoosejs.com/docs/api.html#document_Document-populate
Here is an example from a link above.
doc
.populate('company')
.populate({
path: 'notes',
match: /airline/,
select: 'text',
model: 'modelName'
options: opts
}, function (err, user) {
assert(doc._id == user._id) // the document itself is passed
})
Even though this is rather an old post, I'd like to share a solution through the MongoDB aggregation lookup pipeline
The important part is this:
{
$lookup: {
from: 'spells',
localField: 'spells',
foreignField:'_id',
as: 'spells'
}
},
{
$project: {
_id: 1,
name: 1,
// project the values from damages in the spells array in a new array called damages
damages: '$spells.damages',
spells: {
name: 1,
damages: 1
}
}
},
// take the maximum damage from the damages array
{
$project: {
_id: 1,
spells: 1,
name: 1,
maxDamage: {$max: '$damages'}
}
},
// do the sorting
{
$sort: {'maxDamage' : -1}
}
Find below a complete example
'use strict';
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
mongoose.connect('mongodb://localhost/lotr');
const db = mongoose.connection;
db.on('error', console.error.bind(console, 'connection error:'));
db.once('open', () => {
let SpellSchema = new Schema({
name : { type: String },
damages : { type: Number }
});
let Spell = mongoose.model('Spell', SpellSchema);
let WizardSchema = new Schema({
name: { type: String },
spells: [{ type: Schema.Types.ObjectId, ref: 'Spell' }]
});
let Wizard = mongoose.model('Wizard', WizardSchema);
let fireball = new Spell({
name: 'Fireball',
damages: 20
});
let frozenball = new Spell({
name: 'Frozenball',
damages: 10
});
let lightball = new Spell({
name: 'Lightball',
damages: 15
});
let spells = [fireball, frozenball, lightball];
let wizards = [{
name: 'Gandalf',
spells:[fireball]
}, {
name: 'Saruman',
spells:[frozenball]
}, {
name: 'Radagast',
spells:[lightball]
}];
let aggregation = [
{
$match: {}
},
// find all spells in the spells collection related to wizards and fill populate into wizards.spells
{
$lookup: {
from: 'spells',
localField: 'spells',
foreignField:'_id',
as: 'spells'
}
},
{
$project: {
_id: 1,
name: 1,
// project the values from damages in the spells array in a new array called damages
damages: '$spells.damages',
spells: {
name: 1,
damages: 1
}
}
},
// take the maximum damage from the damages array
{
$project: {
_id: 1,
spells: 1,
name: 1,
maxDamage: {$max: '$damages'}
}
},
// do the sorting
{
$sort: {'maxDamage' : -1}
}
];
Spell.create(spells, (err, spells) => {
if (err) throw(err);
else {
Wizard.create(wizards, (err, wizards) =>{
if (err) throw(err);
else {
Wizard.aggregate(aggregation)
.exec((err, models) => {
if (err) throw(err);
else {
console.log(models[0]); // eslint-disable-line
console.log(models[1]); // eslint-disable-line
console.log(models[2]); // eslint-disable-line
Wizard.remove().exec(() => {
Spell.remove().exec(() => {
process.exit(0);
});
});
}
});
}
});
}
});
});
here's the sample of mongoose doc.
var PersonSchema = new Schema({
name: String,
band: String
});
var BandSchema = new Schema({
name: String
});
BandSchema.virtual('members', {
ref: 'Person', // The model to use
localField: 'name', // Find people where `localField`
foreignField: 'band', // is equal to `foreignField`
// If `justOne` is true, 'members' will be a single doc as opposed to
// an array. `justOne` is false by default.
justOne: false,
options: { sort: { name: -1 }, limit: 5 }
});

Categories

Resources