mongoose deleting and updating in array - javascript

I need to take array from my model, delete from it some days and push to it some other days. It looks something like deleteAndUpdate.
To sum up:
I need to take Car from database. Take reserved property (it's a array), then delete from reserved days from given list, and then add to reserved days from other given list.
My model look:
const CarSchema = mongoose.Schema({
mark:{
type: String,
required: true,},
model:{
type: String,
required: true,},
price:{
type: Number,
required: true,},
available: {
type: Boolean,
required: true,},
reserved:{
type:[Date],
},
pic_1:{
type:String,
required:true,
},
pic_2:{
type:String,
required:true,
},
},
{ collection: 'cars' }
)
I take car by: var car= await Car.findById(carID); and then i need to do sth like that:
car['reserved'].deleted(old_days);
car['reserved'].push(new_days;
car.save();
Could someone help me?

Update can't allow multiple operation at a time in same field, It will throw multiple write error and would create a conflict at your field,
Regular update:
If you want to do it by regular update query you have to do separate do 2 queries,
Delete days: If you want to delete multiple days use $pullAll, and for single you can use $pull
var old_days = [new Date("2021-04-24"), new Date("2021-04-25")];
await Car.updateOne({ _id: carID }, { $pullAll: { reserved: old_days } });
Add days: if you want to add multiple days you can use $push with $each, and for single you can use just $push,
var new_days = [new Date("2021-04-26"), new Date("2021-04-27")];
await Car.updateOne({ _id: carID }, { $push: { reserved: { $each: new_days } } });
Update with aggregation pipeline:
If you are looking for single query you can use update with aggregation pipeline starting from MongoDB 4.2,
$filter to iterate loop of reserved array and remove old days
$concatArrays to concat reserved array with new days
var old_days = [new Date("2021-04-24"), new Date("2021-04-25")];
var new_days = [new Date("2021-04-26"), new Date("2021-04-27")];
await Car.updateOne(
{ _id: carID },
[{
$set: {
reserved: {
$filter: {
input: "$reserved",
cond: { $not: { $in: ["$$this", old_days] } }
}
}
}
},
{
$set: {
reserved: {
$concatArrays: ["$reserved", new_days]
}
}
}]
);
Playground

To remove old item from array you can use $pull
car.update(
{ _id: carID },
{ $pull: { 'reserved': old_days } }
);
You can use $unset to unset the value in the array (set it to null), but not to remove it completely.
To add the item new_days in array, You can either use $push or $addToSet
car.update(
{ _id: carID },
{ $push: { 'reserved': new_days } }
);

Related

How to get an array element in the same index as the query in mongoose

I have the following Schema:
const PublicationSchema = mongoose.Schema({
title: {
type: String,
required: true
},
files:[{
contentType: String,
data: Buffer,
name: String
}]
})
What I'm trying to do is to get the file with the same index as the query.For example I have this object:
_id: new ObjectId("637f20ce6ce5c48d9788a1ff"),
title: 'TEST',
files: [
{
contentType: 'application/pdf',
name: 'imId1',
_id: new ObjectId("id1")
},
{
contentType: 'application/pdf',
name: 'imId2',
_id: new ObjectId("id2")
}
]
where if I query id2 it only retrieves:
{
contentType: 'application/pdf',
name: 'imId2',
_id: new ObjectId("id2")
}
What I was trying to use was const onePublication = await Publication.findOne({ "files._id": req.body.fileId},{}) but this retrieves every field.
I was going to just tell it to not retrieve the other field using field:0 but I realized that this will still retrieve the files in other indexes of the field.
Is there a way to tell it to only retrieve the one with the same index or should I be using another query entirely?
One of the options is to $unwind the array first. $match by your criteria. Then, $replaceRoot to get your array entry.
db.collection.aggregate([
{
"$unwind": "$files"
},
{
$match: {
"files._id": "id2"
}
},
{
"$replaceRoot": {
"newRoot": "$files"
}
}
])
Mongo Playground
Consider changing your schema to store files as an individual collection, if most of the time you are going to access the array objects only.

Update boolean value inside of nested array Mongoose

I have a Schema that holds an array of objects for comments and I would like to update the boolean value of the flagged comments accordingly, I have tried updateOne and aggregate but it isn't working out at this point, I have also tried to use $elemMatch but it isn't working.
The comment _id is being pulled from the front end element that has an ID that is the same as the id that needs to be pulled from MongoDB.
Comments Array within the question Schema:
comments: [
{
user: {
type: Object,
},
commentDate: {
type: Date,
default: Date.now()
},
flagged: {
type: Boolean,
default: false
},
flaggedDate:{type: Date},
comment: String,
}
],
function I tried to run last.
const id = req.params.id
const updateFlag = Question.updateOne(
{
comments: [
{
_id: id
}
]
},
{
$set: {
comments: [
{
flagged: req.body.flagged
}
]
}
}
)
Any help would be appreciated!
You can do it with positional operator - $:
db.collection.update({
"comments._id": "3"
},
{
"$set": {
"comments.$.flagged": true
}
})
Working example

Mongoose find results between 2 dates from html datepicker

I have the following sub-documents :
{
id: 1,
date:2019-04-01 00:21:19.000
},
{
id: 2,
date:2019-03-31 00:21:19.000
} ...
Document schema is :
const barEventSchema = new Schema({
id: {
type: Number,
unique: true,
required: true
},
raw: { type: String },
date: { type: Date },
type: { type: String },
})
const FooSchema = new Schema({
bar: [barEventSchema]
})
I want to do a query based on a date range picked from html input, which has values like 2019-04-01, 2019-03-31.
So on serverside, I want to do something like:
//#star_date = 2019-04-01, #end_date = 2019-04-01
Foo.findOne('bar.date' : {$lte : start_date, $gte: end_date})
However, this returns all the documents.
All documents having any subdocument with date between start and end date range can be retrieved using:
const conditions = {
'bar': {
$elemMatch: {
'date': {
$gte: new Date(start_date),
$lte: new Date(end_date)
}
}
}
}
Foo.find(conditions)
This will return all the documents where there is at least a subdocument having its date between the range specified in condition.
The $elemMatch operator is used to effect this condition on the date field of the bar subdocument.

Why parameter multi doesn't work in mongo request?

I try to update several items in mongo collection with a single request:
// [1, 2, 3] - numbers array.
const days = req.body.days;
const updated = await Item.update(
{shift: shiftId, day: {$in: days}},
{multi : true},
{update: {
name: 'one value for all objects witch corresponding condition',
},
function(err, docs) {
console.log(docs);
}
);
This Item schema:
const itemSchema = new Schema({
shift: {
ref: 'shift',
type: Schema.Types.ObjectId,
required: true
},
day: {
type: Number,
required: true
},
name: {
type: String
}
});
But then I call this code updating only one objects.
I had many Items with have the same shift. But every Item had a unique day and I need update all Items which contains in days array.
For example, if we have shiftId = 'abc' and days = [1, 2], I need update all Items which have shiftId = 'abc' and have day = 1 OR day = 2 both should be updated.
Why my solution have unexpected behavior and updating only one object meanwhile I set {multi : true}? How to fix it? Thank You.
multi: true is the last parameter in an update query and you have used it second parameter.
So you have to use multi: true in last parameter and need to use $set to update a field.
const updated = await Item.update(
{ "shift": shiftId, "day": { "$in": days }},
{ "$set": { "name": "one value for all objects witch corresponding condition" }},
{ "multi": true }
)

Is It possible to use query projection on the same collection that has a $elemMatch projection?

I understand that you can limit the items in a subcollection array using $elemMatch as a projection. When using it as such it returns all fields of the subdocuments that match regardless if query projections are also specified.
Is it possible to limit the fields returned in the matching subdocuments? How would you do so?
Using version 3.8.9.
Given the simple schemas:
var CommentSchema = mongoose.Schema({
body: String,
created: {
by: {
type: String,
required: true
},
date: {
type: Date,
default: Date.now
}
}
});
var BlogSchema = mongoose.Schema({
title: String,
blog: String,
created: {
by: {
type: String,
required: true
},
date: {
type: Date,
default: Date.now
}
},
comments: [CommentSchema]
});
var Blog = mongoose.model('Blog',modelSchema);
Example
Blog.findOne({_id: id}, {_id: 1, comments: {$elemMatch: {'created.by': 'Jane'}, body: 1}}, function(err, blog) {
console.log(blog.toJSON());
});
// outputs:
{
_id: 532cb63e25e4ad524ba17102,
comments: [
_id: 53757456d9c99f00009cdb5b,
body: 'My awesome comment',
created: { by: 'Jane', date: Fri Mar 21 2014 20:34:45 GMT-0400 (EDT) }
]
}
// DESIRED OUTPUT
{
_id: 532cb63e25e4ad524ba17102,
comments: [
body: 'My awesome comment'
]
}
Yes there are two ways to do this. So you can either use the $elemMatch on the projection side as you already have, with slight changes:
Model.findById(id,
{ "comments": { "$elemMatch": {"created.by": "Jane" } } },
function(err,doc) {
Or just add to the query portion and use the positional $ operator:
Model.findOne(
{ "_id": id, "comments.created.by": "Jane" },
{ "comments.$": 1 },
function(err,doc) {
Either way is perfectly valid.
If you wanted something a little more involved than that, you can use the .aggregate() method and it's $project operator instead:
Model.aggregate([
// Still match the document
{ "$match": "_id": id, "comments.created.by": "Jane" },
// Unwind the array
{ "$unwind": "$comments" },
// Only match elements, there can be more than 1
{ "$match": "_id": id, "comments.created.by": "Jane" },
// Project only what you want
{ "$project": {
"comments": {
"body": "$comments.body",
"by": "$comments.created.by"
}
}},
// Group back each document with the array if you want to
{ "$group": {
"_id": "$_id",
"comments": { "$push": "$comments" }
}}
],
function(err,result) {
So the aggregation framework can be used for a lot more than simply aggregating results. It's $project operator gives you more flexibility than is available to projection using .find(). It also allows you to filter and return multiple array results, which is also something that cannot be done with projection in .find().

Categories

Resources