This is my schema
var BudgetSchema = mongoose.Schema({
user_email: { type: String, required: true },
user_budget: { type: Number, required: true },
platforms_budget: {
type: [{
platform_id: { type: String, required: true },
platform_name: { type: String, required: true },
platform_budget_percent: { type: Number, required: true },
platform_budget: { type: Number, required: true },
}]
},
created: { type: Date, default: Date.now() }
});
var BudgetSchemaExport = module.exports = mongoose.model('Budget', BudgetSchema);
And this is the function:
module.exports.updateBudgetPercent = async function (user_email, platform_name, p_budget) {
var query = {
"user_email": user_email,
"platforms_budget": {
$elemMatch: { "platform_name": platform_name }
}
};
//the location that need to be updated
var update = { "$set": { "platforms_budget.$[outer].platform_budget_percent": p_budget } };
//array's index
var options = { arrayFilters: [{ "outer.platform_name": platform_name }] };
var updated = await this.findOneAndUpdate(query, update, options).sort({ created: -1 })
if (updated) { //if the data was updated
return true;
} else {
return false;
}
}
The purpose of the function is to update a budget for specific user.
I want to update the latest document, but it updates the earliest/first one.
I tried this option: var updated = await this.findOneAndUpdate(query, update, options, {sort:{ "created": -1 }}), but I got an error:
events.js:167
throw er; // Unhandled 'error' event
^
TypeError: callback.apply is not a function
Any ideas? Thanks.
Use upsert: true with sort: { created: -1 }. Here is the query:
var updated = await this.findOneAndUpdate(
query,
update,
{
upsert: true,
sort: { created: -1 },
}
);
Hope this helps you.
Related
I have the following schema for a grid (evaluation grid). Grid => Sections => Criteria => Levels and I want to update a single Level element.
const mongoose = require("mongoose");
const levelSchema = mongoose.Schema(
{
title: {
type: String,
required: true,
},
value: {
type: Number,
required: true,
},
},
{ timestamps: true }
);
exports.Level = mongoose.model("Level", levelSchema);
const criterionSchema = mongoose.Schema(
{
title: {
type: String,
required: true,
},
levels: [levelSchema],
},
{ timestamps: true }
);
criterionSchema.virtual("weight").get(function () {
return Math.max(this.levels.map((level) => level.weigth));
});
exports.Criterion = mongoose.model("Criterion", criterionSchema);
const sectionSchema = mongoose.Schema(
{
name: {
type: String,
required: true,
},
criteria: [criterionSchema],
},
{ timestamps: true }
);
sectionSchema.virtual("weight").get(function () {
return this.criteria.reduce((acc, criteria) => acc + criteria.weight, 0);
});
exports.Section = mongoose.model("Section", sectionSchema);
const schema = mongoose.Schema(
{
name: {
type: String,
required: true,
},
sections: [sectionSchema],
code: { type: Number, required: true },
course: {
type: mongoose.Schema.Types.ObjectId,
ref: "Course",
required: true,
},
},
{ timestamps: true }
);
schema.virtual("weight").get(function () {
return this.sections.reduce((acc, section) => acc + section.weight, 0);
});
exports.Grid = mongoose.model("Grid", schema);
I was able to retrieve a specific Level's Grid with this code :
Grid.findOne({"sections.criteria.levels._id": levelId})
So I tried FindOneAndUpdate with this code :
const grid = await Grid.findOneAndUpdate(
{ "sections.criteria.levels._id": req.params.levelId },
{
$set: {
"sections.$[].criteria.$[].levels.$[].title": req.body.title,
},
},
{ new: true });
But, it changed ALL the Levels of the grid.
How can we update a single Level sub, sub, sub document and returns it ?
I want to find all the documents whose getters return a particular value. Specifically, in my Position schema, I have a property called uncoveredQuantity, and I want to find all Position documents with uncoveredQuantity = 10.
When I do
const positions = await Position.find({uncoveredQuantity : 10});
it returns all documents with various different uncoveredQuantity values, not the ones that are 10! The interesting thing is that my getter function, calculateUncoveredQuantity, does get called when Position.find() gets called, and it prints the console.log statement.
function calculateUncoveredQuantity(this: PositionBaseDocument) {
console.log("getter called");
let quantityLeft = this.quantity;
const dest = this.direction === "in" ? this.closedBy : this.isCloseFor;
if (!dest) return quantityLeft;
for (const pos of dest) {
quantityLeft -= pos.amount;
}
return quantityLeft;
}
const PositionCloseSchema = {
position: {
type: mongoose.Schema.Types.ObjectId,
ref: "Position",
},
amount: {
type: Number,
min: 0,
},
};
const positionSchema = new mongoose.Schema(
{
direction: {
type: String,
enum: CONSTANTS.directions,
required: true,
},
quantity: {
type: Number,
required: true,
min: 0,
},
uncoveredQuantity: {
type: Number,
set: calculateUncoveredQuantity,
default: calculateUncoveredQuantity,
get: calculateUncoveredQuantity,
},
closedBy: {
type: [PositionCloseSchema],
required: function (this: PositionBaseDocument) {
return this.direction === "in";
},
},
isCloseFor: {
type: [PositionCloseSchema],
required: function (this: PositionBaseDocument) {
return this.direction === "out";
},
},
},
{
timestamps: true,
toJSON: {
getters: true,
},
toObject: {
getters: true
},
}
);
export const Position = mongoose.model("Position", positionSchema);
I've noticed there's little documentation and info about how I should manipulate an array of objects using Mongoosejs.
I have the following model/Schema for an User:
'use strict';
/**
* Module Dependencies
*/
var bcrypt = require('bcrypt-nodejs');
var crypto = require('crypto');
var mongoose = require('mongoose');
/**
* Custom types
*/
var ObjectId = mongoose.Schema.Types.ObjectId;
var userSchema = new mongoose.Schema({
email: { type: String, unique: true, index: true },
password: { type: String },
type: { type: String, default: 'user' },
facebook: { type: String, unique: true, sparse: true },
twitter: { type: String, unique: true, sparse: true },
google: { type: String, unique: true, sparse: true },
github: { type: String, unique: true, sparse: true },
tokens: Array,
profile: {
name: { type: String, default: '' },
gender: { type: String, default: '' },
location: { type: String, default: '' },
website: { type: String, default: '' },
picture: { type: String, default: '' },
phone: {
work: { type: String, default: '' },
home: { type: String, default: '' },
mobile: { type: String, default: '' }
}
},
activity: {
date_established: { type: Date, default: Date.now },
last_logon: { type: Date, default: Date.now },
last_updated: { type: Date }
},
resetPasswordToken: { type: String },
resetPasswordExpires: { type: Date },
verified: { type: Boolean, default: true },
verifyToken: { type: String },
enhancedSecurity: {
enabled: { type: Boolean, default: false },
type: { type: String }, // sms or totp
token: { type: String },
period: { type: Number },
sms: { type: String },
smsExpires: { type: Date }
},
friends: [{
friend: { type: ObjectId, ref: 'User' },
verified: { type: Boolean, default: false }
}]
});
/* (...) some functions that aren't necessary to be shown here */
module.exports = mongoose.model('User', userSchema);
So as you can check I defined Friends inside User like this:
friends: [{
friend: { type: ObjectId, ref: 'User' },
verified: { type: Boolean, default: false }
}]
Now the question is how can I add, edit and delete this array in a Node.js script?
BOTTOMLINE: How can I manipulate arrays that are inside MongoDB Schemas, using Node.js and Mongoose.js? Do I always have to create a Schema function or can I access it directly?
EDIT (13/07/2014): So far I've created a HTTP GET that gives me the array like this:
app.get('/workspace/friends/:userid', passportConf.isAuthenticated, function (req, res) {
User.find({_id: req.params.userid}, function (err, items) {
if (err) {
return (err, null);
}
console.log(items[0].friends);
res.json(items[0].friends);
});
});
But this only returns an array of friendIds, but what if I want to create some sort of '/workspace/friends/:userid/del/:friendid' POST, or add POST. I can't seem to figure out how I can get this done.
You can do something like following
app.get('/workspace/friends/:userid/delete/:friendId', passportConf.isAuthenticated, function (req, res) {
User.findOne({_id: req.params.userid}, function (err, user) {
if (err) {
return (err, null);
}
for (var i = 0; i < user.friends.length; i++) {
if (user.friends[i]._id === req.params.friendId) {
user.friends = user.friends.splice(i,1)
}
}
user.save(function(err, user, numAffected){
if (!err )res.json(user)
res.send('error, couldn\'t save: %s', err)
})
});
});
What it says in mongoose docs is that
"The callback will receive three parameters, err if an error occurred, [model] which is the saved [model], and numberAffected which will be 1 when the document was found and updated in the database, otherwise 0.
The fn callback is optional. If no fn is passed and validation fails, the validation error will be emitted on the connection used to create this model."
If you need to manipulate arrays, you should convert these in objects before.
User.findOne({_id: req.params.userid}, function (err, user) {
if (err) {
return (err, null);
}
var user = user.toObject();
//... your code, an example =>
delete user.friends;
res.json(user);
});
Regards, Nicholls
I have the following user model:
var model = module.exports = {
autoPK: false,
attributes: {
id: {
type: 'string',
primaryKey: true
},
email: {
type: 'string',
required: true
},
hash: {
type: 'string',
required: true
},
}
}
And the following query on it:
User.findOne({_id: req.param('user_id')}).populate('drafts').then(function(user) {
console.log("USER: " + JSON.stringify(user) + " FOR: " + req.param('user_id'));
res.send(user.drafts, 200);
});
From the print statement, I know nothing is turning being returned for the ID "Rfrq8un5f," but the mongodb command line outputs this:
> db.user.find();
{ "email" : "m#m.com", "hash" : "[...]", "createdAt" : ISODate("2014-05-18T16:32:21.023Z"), "updatedAt" : ISODate("2014-05-18T16:32:21.023Z"), "_id" : "9PTIqHxEc" }
What's going on?
To solve the id: null when use waterline with mongo adapter you must add to the model: autoPK: false, schema: true. You need both, isn't enough with autoPK false or schema true.
This is a model example solving that issue (user model):
module.exports = {
schema: true,
autoPK: false,
attributes: {
name: {
type: 'string',
required: true
},
email: {
type: 'string',
email: true,
required: true,
unique: true
},
password: {
type: 'string',
minLength: 6,
maxLength: 15,
columnName: 'encrypted_password',
required: true
},
toJSON: function() {
var obj = this.toObject();
delete obj.password;
return obj;
}
},
beforeCreate: function(values, next) {
require('bcrypt').hash(values.password, 10, function passwordEncrypted(err, encryptedPassword) {
if(err) console.log(err);
values.password = encryptedPassword;
next();
});
}
};
There is my code:
var fileModel = context.models.File,
query = {
_id: context.models.ObjectId("532083358ab1654c0c8b4ced") // TODO: for debug, change after update fix
},
update = {
description: context.data.description,
userId: context.data.userId ?
context.models.ObjectId(context.data.userId) : undefined,
isAdded: true
};
fileModel.update(query, update, { multi: true }, function (err) {
if (err) {
console.log('update');
console.log(err);
context.sendJson({ success: false, err: err });
}
else {
context.sendJson({ success: true });
}
});
There is my Schema:
var fileSchema = new schema({
path: { type: String, required: true, validate: [validateName, 'a path is required'] },
isApproved: { type: Boolean, default: false },
isAdded: { type: Boolean, default: false },
name: { type: String, required: true, validate: [validateName, 'a name is required'] },
description: { type: String },
userId: { type: schema.Types.ObjectId },
updated: { type: Date, default: Date.now },
size: { type: Number }
}, { autoIndex: false });
When I try to update document by id I see this messages in console:
update
[TypeError: Cannot read property '_id' of undefined]
I think problem in
userId: context.data.userId ?
context.models.ObjectId(context.data.userId) : undefined,
But I don't understand how fix it.
I solve this by separate part of my code. But I can't understand what's wrong in my first solution. That's working code:
var fileModel = context.models.File,
query = {
_id: {
$in: context.data.files.map(function (el) {
return context.models.ObjectId(el);
})
}
},
update = {
description: context.data.description,
isAdded: true
};
if (context.data.userId){
update.userId = context.models.ObjectId(context.data.userId);
}
fileModel.update(query, update, { multi: true }, function (err) {
if (err) {
console.log('update');
console.log(err);
context.sendJson({ success: false, err: err });
}
else {
context.sendJson({ success: true });
}
});