How can i make this update work?
current error:
MongoError: cannot use the part (cartheft of crimes.0.cartheft.chance) to traverse the element
i also tried to put $, but then i get:
(node:10360) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 2): MongoError: Too many positional (i.e. '$') elements found in path 'crimes.$.cartheft.$.chance'
code:
cartheft_crime.update({
userid: req.user._id,
"crimes.location": 1,
"crimes.cartheft.theftid" : 1,
}, {$inc: {"crimes.$.cartheft.chance": 1000}}).then(function (response) {
res.json(response);
});
model:
userid : String,
crimes: [{
location: {type: Number, default: 1},
lastcartheft: {
time: {type: Number, default: 1},
type: {type: Number, default: 1},
},
cartheft: [
{
id: {type: Number, default: 1},
theftid: {type: Number, default: 1},
chance: {type: Number, default: 200},
success: {type: Number, default: 0},
failure: {type: Number, default: 0},
},
],
}],
ip: String
Looking at the documentation here is how the positional operator $ handle arrays :
Nested Arrays
The positional $ operator cannot be used for queries which traverse
more than one array, such as queries that traverse arrays nested
within other arrays, because the replacement for the $ placeholder is
a single value
So you cannot perform the increment that way. You should retrieve the data, modify it programmatically and then save the change.
For example :
// Get the user data
cartheft_crime.findOne({
userid: req.user._id,
})
.then((ret) => {
// We have no user behind req.user._id
if (!ret) throw new Error('Cannot find the user');
// Modify the data
const user_obj = ret;
// Get the right crime to modify
const right_crime = user_obj.crimes.find(x => x.location === 1);
// Cannot find it
if (!right_crime) throw new Error('Cannot find the appropriate crime');
// Get the right cartheft to modify
const right_cartheft = right_crime.cartheft.find(x => x.theftid === 1);
// Cannot find it
if (!right_cartheft) throw new Error('Cannot find the appropriate cartheft');
// Finally modify the data
right_cartheft.chance += 1;
// Save the data
return user_obj.save();
})
.then(() => {
// It's done !
})
.catch((err) => {
// Send the error ...
});
Related
I've got a Mongoose schema set up as follows:
const mongoose = require('mongoose');
const TodoSchema = mongoose.Schema({
id: {
type: String,
required: true,
},
todos: {
type: Array,
required: true,
},
date: {
type: Date,
default: Date.now(),
},
});
module.exports = mongoose.model('todo', TodoSchema, 'todos');
Each of the elements in the todos Array is an Object and has the following format (example):
{
id: 1,
todo: "Do the dishes."
category: "Kitchen"
}
There are multiple documents in my Todo collection and they all contain the same list of Todos. If I wanted to update a specific Todo across ALL documents, I figure I need to use updateMany. I'm using the following in my Todo Update route to update all instances of a Todo:
const { todo } = req.body; // todo.todo contains "Clean the dishes." as an update
Todo.updateMany(
{
todos: { $elemMatch: { id: todo.id } },
},
{ $set: { todo: todo } }
);
I'm assigning the result of the above route code to a variable and console logging the result which comes back with:
{ ok: 0, n: 0, nModified: 0 }
What am I doing wrong? The passed todo id matches the id of a Todo in each of the Todos arrays.
First of all, for your object array, is recommendable create a schema too:
const subSchema = new mongoose.Schema({
id: Number,
todo: String,
category: String
})
const MongooseModel = new mongoose.Schema({
id: String,
todos: [subSchema],
date: Date
})
So now, your array object is defined.
And, the query question is something like that:
db.collection.update({
"todos.id": todo.id
},
{
"$set": {
"todos.$": {newTodo}
}
},
{
"multi": true
})
First, you look for all elements that match your criteria; that is: todos.id = todo.id, then you use $ operator to set all element that match the criteria with your object.
The last line multi is to updated all element that match.
Example playground here
Using moongoose, multi attribute is not neccessary because is set true by default using updateMany().
So moongose query should be something like that.
var update = await model.updateMany(
{
"todos.id": 1
},
{
"$set": {
"todos.$": {
"id": 20,
"todo": "newTodo",
"category": "newCategory"
}
}
})
And for this example data the result is
{ n: 3, nModified: 3, ok: 1 }
Im getting validation error at path badgeid, why am i getting this when my getting it when badgeid is a number, and what im saving is a number?`
Checks that i already checked:
achievement value is 5,
achievement type is Number
error:
(node:8260) UnhandledPromiseRejectionWarning: ValidationError: achievement_users validation failed: badgeid: Cast to Number failed for value "{ badgeid: 0,
progress: 0,
_id: 5c94c04a758c8a204440499e }" at path "badgeid"
save code:
var achievement = new achivementUsers();
achievement.badgeid = achievement;
return achievement.save().then(function (response) {
schema:
{
badgeid: {type: Number, default: 0},
progress: {type: Number, default: 0},
completed: {type: Boolean, default: false},
userid: {type: String, default: 'No name'},
}
You are overriding the variable achievement. You are setting achievement with the new model so you need to use another name instead:
//somewhere in the code `achievement` is 5
var achievementUser = new achivementUsers();
achievementUser.badgeid = achievement;
return achievementUser.save().then(function (response) {});
Or you can initialize the value when you create the model object. But it is still a good practice to use different variable names for different context.
//somewhere in the code `achievement` is 5
var achievement = new achivementUsers({
badgeid: achievement
});
return achievement.save().then(function (response) {});
I'm trying to use floats as keys in a mongodb database since I have to store the respective amount of each money value (e.g. 0.01: 10). But when I try saving the data via mongoose (node server using express) it only saves the data with normal (String) keys in the db.
This is my schema:
var ProtokollSchema = new Schema({
"date": String,
"0.01": {type: Number, default: 0},
"0.02": {type: Number, default: 0},
"0.05": {type: Number, default: 0},
"0.10": {type: Number, default: 0},
"0.20": {type: Number, default: 0},
"0.50": {type: Number, default: 0},
"1.00": {type: Number, default: 0},
"2.00": {type: Number, default: 0},
...
});
This is the express function setting the data:
.post(function(req, res) {
var protokoll = new ProtokollSchema();
protokoll["date"] = req.body["date"];
protokoll["0.01"] = req.body.data["0.01"];
protokoll["0.02"] = req.body.data["0.02"];
protokoll["0.05"] = req.body.data["0.05"];
protokoll["0.10"] = req.body.data["0.10"];
protokoll["0.20"] = req.body.data["0.20"];
protokoll["0.50"] = req.body.data["0.50"];
protokoll["1.00"] = req.body.data["1.00"];
protokoll["2.00"] = req.body.data["2.00"];
...
protokoll.save(function(err) {
if (err) res.json(err);
res.json({ message: "Comment successfully added!"});
});
})
Is there a solution or is it just not possible to do?
All keys in a schema must be a string whatever the key string looks like a normal string, a float or others.
Make sure that req.body.data be really with values like {"0.01": xxx, "0.02": xxx, ...};
You should use Model to create a document instead of Schema
//wrong way
var protokoll = new ProtokollSchema();
//Right way - Use the schema to generate a model and use model to new a docuemnt.
var Protokoll = mongoose.model('Protokoll', ProtokollSchema);
var protokoll = new Protokoll({"0.01": xxx, "0.02": xxx, ...});
The actual problem was that MongoDB simply doesn't support dots in its keys (see this question) Initially I thought the problem was the Schema of mongoose but obviously it wasn't.
I'm learning to use Node.js + Express to build a REST API. In this API
I have the following method:
apiRouter.route('/training/session/byId/:id_session')
// ===== GET =======
.get(function(req, res) {
//Get the session
Session.findById(req.params.id_session, function(err, session) {
//Get an array of exercise associated with the session
Exercise.find({session: session._id}, function(err, exercise) {
let movements = [];
let sets = [];
let i = exercise.length-1;
//For every exercise get the movements and the sets
exercise.forEach(function (ex,index) {
Movement.findById(ex.movement,function(err,movement){
if(movement)
movements.push(movement);
//***** Here?
Set.find({exercise: ex}, function (err, set) {
if(set.length)
sets.push(set);
if(index == i){
res.json({ message: 'ok' ,session,exercise,movements,sets});
}
})
})
})
});
});
})
The idea is obtain all the session related information from the database.
First:
I think that is not the correct way of make multiple querys and return an object with the info of all the querys, but I'm novice with the async working of Node... So what is the correct way to make multiple querys where the data of one query depends of other query?
Second: In the Front-End (React + Redux) I'm making Ajax request with axios and for the same Ajax request sometimes not all 'sets' are fetched (//***** Here?). The problem is in the API?
Thanks in advance.
EDIT: DB models
Session:
var SessionSchema = new Schema({
date: {type: Date, default: Date.now },
time: Number, //Time in seconds
user: {required: true, type: mongoose.Schema.Types.ObjectId, ref: 'User'},
});
Exercise:
var ExerciseSchema = new Schema({
session: {type: mongoose.Schema.Types.ObjectId, ref: 'Session'},
movement: {type: mongoose.Schema.Types.ObjectId, ref: 'Movement'},
timestamp: {
type: Date,
default: Date.now
}
});
Set:
var SetSchema = new Schema({
repetitions: Number,
weight: Number,
rest: Number,
timestamp: {
type: Date,
default: Date.now
},
exercise : {type: mongoose.Schema.Types.ObjectId, ref: 'Exercise'}
});
Movement:
var MovementSchema = new Schema({
name: { type: String, required: true, index: true, unique: true },
material:{ type: String, required: true},
muscles : [{
name : { type: String, required: true},
percentage : { type: Number, required: true}
}]
});
Set.find({exercise: ex}, function (err, set) {
if(set.length)
sets.push(set);
}).then(function(set){
if(index == i){
res.json({ message: 'ok' ,session,exercise,movements,sets});
}
})
Of course my prev answer wouldn't work. The set query callback would execute after the if(index == i). Actually I'm not sure this will produce different results from your code. I've never actually used Mongoose but as far as I know, you can't do joins so nested queries is the way to do it.
You might want to consider using promises. Not necessary, but they make your code easier to read and think about: http://eddywashere.com/blog/switching-out-callbacks-with-promises-in-mongoose/
It might make more sense as well to create a single result object that you build up as the queries return so you end up sending a single JSON object representing your session that looks like:
{
exercises: [
{
sets: [],
movements: [],
},
{
sets: [],
movements: [],
},
...
]
}
I'm new to Mongoose and I'm having difficulty getting my head around accessing properties deeper in the model and updating properties on the following model structures.
Game Schema
var gameSchema = new Schema({
opponents: [{
type: Schema.Types.ObjectId,
ref: 'teams'
}],
startTime: { type: Date, default: Date.now },
endTime: Date,
pauses: [{
start: Date,
end: Date
}],
winner: {
type: Schema.Types.ObjectId,
ref: 'teams'
},
status: {type: String, default: "created"},
score: [{
"opponent1": {type: Number, default: 0},
"opponent2": {type: Number, default: 0}
}],
}, { versionKey: false });
Team Schema
var teamSchema = new Schema({
name:String,
badge:String,
goals:[{type: Date, default: Date.now}],
totalWins:Number
}, { versionKey: false });
My problem is I'm trying to add a goal to a team from a specific game.
So my end point:
POST: /api/game/GAME_ID/goal
DATA: {_id: TEAMID}
I thought the following would work:
Games.findById(GAME_ID)
.populate('opponents')
.find({'opponent._id': TEAM_ID})
.exec(function(err, team) {
// Team from game with matching ID returned
// Now push goal time into array
team.goal.push(Date.now());
});
The above does not appear to return a team. if I remove the second find the game is returned and then I have to do something horrible like this:
Games.findById(GAME_ID)
.populate('opponents')
.exec(function(err, game) {
if(game.opponents[0]._id.toString() === req.body._id) {
game.opponents[0].goals.push(Date.now());
} else if (game.opponents[1]._id.toString() === req.body._id) {
game.opponents[1].goals.push(Date.now());
} else {
// Throw error no matching team with id
}
});
game.save(function(err, game) {
//Game saved
});
this last example appears to work but when I try to add further goals pushing into the goals array it overwrites the old goal time.
So to recap.
How do I query the Games model to retrieve a child by id which has
yet to be populated?
How do I set push the goal time stamp into the goals array without
overwriting the previous one?
Is it possible to do these a bit more gracefully than the current example given above.
Games.findById(GAME_ID)
.populate(
path: 'opponents',
match: { _id: TEAM_ID}
)
.exec(function(err, team) {
// Team from game with matching ID returned
});