How do you update a nested array with Mongoose? - mongodb

This is what I have so far. This is my AnswerSchema with a comments array nested within that I am trying to update.
const AnswerSchema = new Schema({
user: {
type: Schema.Types.ObjectId,
ref: 'user',
},
question: {
type: Schema.Types.ObjectId,
ref: 'question',
},
text: {
type: String,
required: true,
},
name: {
type: String,
},
avatar: {
type: String,
},
views: {
type: Number,
},
date: {
type: Date,
default: Date.now,
},
answerLikes: [
{
user: {
type: Schema.Types.ObjectId,
ref: 'user',
},
},
],
comments: [
{
user: {
type: Schema.Types.ObjectId,
ref: 'user',
},
text: {
type: String,
required: true,
},
name: {
type: String,
},
avatar: {
type: String,
},
commentLikes: [
{
user: {
type: Schema.Types.ObjectId,
ref: 'user',
},
},
],
date: {
type: Date,
default: Date.now,
},
},
],
})
and here is my update route that I am trying to use to update the comments array text field
try {
const updatedAnswer = await Answer.findOneAndUpdate(
{ _id: req.params.answer_id },
{
$set: { 'comments.$[comment].text': formattedAnswer },
},
{
arrayFilters: [{'comment._id': req.params.comment_id }],
},
{ new: true }
)
res.json(updatedAnswer)
I keep getting the error 'Callback must be a function, got [object Object]' and cant figure out a fix.
Any ideas?
Thanks!

The problem in your code is that you are passing 4 parameters to the findOneAndUpdate function.
The 4th argument is a callback which accepts a function:
(err /* an error if occurred */, doc /* the updated document */) => {}
In order to solve that you need to combine your last 2 arguments into one object like:
{
arrayFilters: [{'comment._id': req.params.comment_id }],
new: true
}
Final query:
const updatedAnswer = await Answer.findOneAndUpdate(
{ _id: req.params.answer_id },
{
$set: { 'comments.$[comment].text': formattedAnswer },
},
{
arrayFilters: [{'comment._id': req.params.comment_id }],
new: true
}
)

The 4th argument in findOneAndUpdate function takes in a callback function that was where your error was.
Try this
try{
const updatedAnswer = await Answer.findOneAndUpdate(
{ _id: req.params.answer_id },
{
$set: { 'comments.$[comment].text': formattedAnswer },
},
{
arrayFilters: [{'comment._id': req.params.comment_id }],
new: true
}
);
res.json(updatedAnswer);
}catch(err){
//console.log(err)
}

Related

Look up and create or update object inside array

I am currently trying to setup a schema for custom Discord guild commands:
const GuildCommandsSchema = new mongoose.Schema({
_id: String,
commands: [
{
name: {
type: String,
unique: true,
required: true,
},
action: {
type: String,
required: true,
},
author: {
type: String,
required: true,
},
},
],
});
Is this ok, performancewise, or could I improve it?
I feel like Mongo would need to look through all commands, since it can't index any commands inside 'commands' even though 'name' is unique.
If that's fine, how can I access the values inside commands?
I would need to find the right command via 'name' if it exists, otherwise create it and add/update 'action' + 'author'.
I tried something like this:
const updatedCommand = await GuildCommands.findOneAndUpdate(
{ _id },
{
$set: {
[`commands.$[outer].name`]: name,
[`commands.$[outer].action`]: action,
[`commands.$[outer].author`]: author,
},
},
{
arrayFilters: [{ 'outer.name': name }],
}
);
Unfortunately that does not create commands if they don't exist.
Thanks for your help
aggregate
db.collection.update({},
{
$set: {
"commands.$[c].name": "1",
"commands.$[c].author": "1",
"commands.$[c].action": "1"
}
},
{
arrayFilters: [
{
"c.author": "34"
}
],
multi: true
})
mongoplayground
To answer my own question:
I changed my Schema to use Maps instead of Arrays for performance improvments and also better model management.
const GuildCommandsSchema = new mongoose.Schema(
{
_id: String,
commands: {
type: Map,
of: {
_id: false,
name: {
type: String,
required: true,
},
action: {
type: String,
required: true,
},
active: {
type: Boolean,
required: true,
default: true,
},
author: {
type: String,
required: true,
},
},
},
},
{ versionKey: false }
);
The new query to find and update/create a command is also better imo:
const findCommand = await GuildCommands.findOne({ _id });
if (!action) {
const getCommand = findCommand.commands.get(name);
if (getCommand) {
message.reply(getCommand.action);
} else {
message.reply(`Cannot find ${name}`);
}
} else {
findCommand.commands.set(name, {
name,
action,
author,
});
findCommand.save();
}

Mongoose find value in each object in array for stored document

I try to find refs I've stored in my db by sending an array of object to my API.
In the array I send there are 4 objects. 2 of them are already stored, 2 of them are new.
Here is what is stored in my DB:
[
{
ref: 'xxx14-010-S',
colorWay: 'Black',
},
{
ref: 'xxx18-050-S',
colorWay: 'Black',
},
...
]
My req.body look like this :
[
{
ref: 'xxx14-010-S',
colorWay: 'Black',
},
{
ref: 'xxx18-050-S',
colorWay: 'Black',
},
{
ref: 'xxx20-010-S',
colorWay: 'Black',
},
{
ref: 'xx324-010-S',
colorWay: 'Black',
}
]
And here is the code in my express router:
router.post("/api/v1/lineList", async (req, res, next) => {
try {
const existingRefs = await LineList.find({ref: {$in: req.body.ref}});
res.send(existingRefs);
} catch (err) {
next(err);
}
});
This returns an empty array where I expect to find the 2 stored objects.
How shall I proceed ?
Is there a way to get the 2 found objects and also be notified that the 2 other object where not in the db ?
Thanks a lot !
EDIT:
Here is the model:
const lineListSchema = new mongoose.Schema({
ref: {
type: String,
required: true,
unique: true,
trim: true,
uppercase: true,
},
styleName: {
type: String,
required: true,
trim: true,
uppercase: true,
},
colorWay: {
type: String,
required: true,
trim: true,
uppercase: true,
},
...
createdAt: {
type: Date,
default: Date.now,
expires: 3600,
},
});
$in syntax is:
{ field: { $in: [<value1>, <value2>, ... <valueN> ] } }
So you need to pass an array like this:
await LineList.find({ ref: { $in: req.body.map(val => val.ref) } });

find one with multiple conditions mongoose mongodb

I am trying to obtain data from mongodb. This is the scenario. Each user has a following property(array) that takes an array of users id.
The user model is as below
const userSchema = new mongoose.Schema({
name: {
type: String,
required: true,
},
email: {
type: String,
required: true,
},
password: {
type: String,
required: true,
},
followers: [{ type: mongoose.Types.ObjectId, ref: "user" }],
following: [{ type: mongoose.Types.ObjectId, ref: "user" }],
});
in simple terms. I need to use two conditions where postedBy: { $in: req.user.following }, or postedBy:req.user._id
const postSchema = new mongoose.Schema({
title: {
type: String,
required: true,
},
body: {
type: String,
required: true,
},
photo: {
type: String,
required: true,
},
likes: [{ type: mongoose.Schema.ObjectId, ref: "user" }],
comments: [
{
text: String,
postedBy: { type: mongoose.Schema.Types.ObjectId, ref: "user" },
},
],
postedBy: {
type: mongoose.Schema.Types.ObjectId,
ref: "user",
},
});
I have not figured out the second condition to add in the code below.
router.get("/getSubPost", requireLogin, async (req, res) => {
try {
const result = await Post.find({
postedBy: { $in: req.user.following },
})
.populate("postedBy", "-password")
.populate("comments.postedBy", "_id name");
res.json({ result });
} catch (error) {
console.log(error);
}
});

sails update the doc, nothing happen, not err message

eventController:
newHelper: function(req, res) {
const eventID = req.body.eventID;
let newHelper = req.body.newHelper;
newHelper.eventAssoc = eventID;
Wapphelprecords.create(newHelper).exec(function(err, newhelper) {
if (err) {
return res.serverError(err); }
sails.log('add new helper:', newhelper);
return res.json(newhelper);
});
}
when I do this action, the database nothing happen, and no err message, this is model under blow:
WappeventController model:
module.exports = {
attributes: {
eventID: {
type: 'integer',
// autoIncrement: true,
unique: true,
// defaultsTo: 0
},
openid: {
type: 'string',
},
author: {
model: 'wappuserinfo'
},
content: {
type: 'string'
},
allowShare: {
type: 'boolean'
},
imageList: {
type: 'array'
},
money: {
type: 'float',
},
helpers: {
collection: 'wapphelprecords',
via: 'eventAssoc',
},
bestHelper: {
collection: 'wapphelprecords',
via: 'eventAssoc',
}
},
Wapphelprecords Model:
module.exports = {
attributes: {
eventAssoc: {
model: 'wappevents',
},
content: {
type: 'string'
},
contact: {
type: 'string'
},
userInfo: {
model: 'wappuserinfo'
},
bestHelper: {
type: 'boolean'
},
moneyEarn: {
type: 'float'
}
}
};
when I do newHelper action, the database nothing happened, and nothing error notice, I just do not understand. need help, thx.
spend whole day to fix it, and finally:
module.exports = {
attributes: {
eventID: {
type: 'integer',
// autoIncrement: true,
primaryKey: true, //<---- set this primarkey to true
unique: true,
// defaultsTo: 0
},
openid: {
type: 'string',
},
author: {
model: 'wappuserinfo'
},
content: {
type: 'string'
},
allowShare: {
type: 'boolean'
},
imageList: {
type: 'array'
},
money: {
type: 'float',
},
helpers: {
collection: 'wapphelprecords',
via: 'eventAssoc',
},
bestHelper: {
collection: 'wapphelprecords',
via: 'eventAssoc',
}
},

Ordering two reference arrays together

Suppose I have the following schemas:
var QuizSchema = new mongoose.Schema({
name: { type: String, required: true },
questions: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Question' }],
questionGroups: [{ type: mongoose.Schema.Types.ObjectId, ref: 'QuestionGroup' }]
});
var QuestionSchema = new mongoose.Schema({
number: { type: String, required: true }, // e.g. 1, a, i, anything
question: { type: String, required: true },
type: { type: String, enum: ['multiple choice', 'multiple select', 'short answer'] },
choices: [String],
answers: [String]
});
var QuestionGroupSchema = new mongoose.Schema({
number: { type: String, required: true }, // e.g. 1, a, i, anything
prompt: { type: String },
questions: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Question' }]
});
I am trying to design a way that will allow me to order questions and question groups together.
I was thinking maybe of adding a new field order
var QuizSchema = new mongoose.Schema({
// ...
order: [
{
type: { type: String, enum: ['Question', 'QuestionGroup'] },
id: mongoose.Schema.Types.ObjectId // reference
}
]
});
such that in the database, the field would contain something such as
[
{ type: 'Question', id: ObjectId('57867a34567g67790') },
{ type: 'Question', id: ObjectId('57867a34567g67765') },
{ type: 'QuestionGroup', id: ObjectId('69864b64765y45645') },
{ type: 'Question', id: ObjectId('57867a34567g67770') },
{ type: 'QuestionGroup', id: ObjectId('69864b64767y45647') }
]
This may mean that I would need to "populate" the ordered list of questions and question groups as
quiz.populate('questions questionGroups').exec(function (err, quiz) {
// sort questions and groups by the order
quiz.order = quiz.order.map(function (o) {
if (o.type === 'QuestionGroup') {
return quiz.questionGroups.id(o.id);
}
return quiz.questions.id(o.id);
});
});
So my question: is there a better way to design this?
Virtuals can come in handy here; without persisting order field in db and doing calculations on client each time:
var QuizSchema = new mongoose.Schema({
name: { type: String, required: true },
questions: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Question' }],
questionGroups: [{ type: mongoose.Schema.Types.ObjectId, ref: 'QuestionGroup' }]
},
{
toObject: {
virtuals: true
},
toJSON: {
virtuals: true
}
}
);
QuizSchema
.virtual('order')
.get(function() {
return this.questions.concat(this.questionGroups); //questions followed by questionGroups
});
Sort on createdAt is of course optional, but for that you need to have this field in Question and QuestionGroup:
Quiz.find({}, function (err, quiz) {
//...
})
.populate({path : 'questions', options: {sort: { 'createdAt': 1 }}})
.populate({path : 'questionGroups', options: {sort: { 'createdAt': 1 }}});