Mongo DB $push object wraps it in an array first - javascript

When I use $push in mongodb, the expected outcome turns out differently. It actually wraps the object I want to push in an array. This is problematic because I'd have to map over the result just to extract it. Any help would be greatly appreciated.
My Mongo Query
const pushAction = {
$push: {
cart: {
id: itemId,
quantity: quantity
}
}
}
// Add item to user's cart
User.update({_id: userId}, pushAction, (err, success) => {
if (err) {
res.status(422).json({'error': 'There was a problem adding the item to your cart.'});
}
if (success) {
// Find user and return the cart
User.findOne({_id: userId}, {cart: 1}, (err, user) => {
res.status(200).json({'message': 'The item was successfully added to your cart.', cart: user.cart});
})
}
});
User Schema
// Define User Model
const userSchema = new Schema({
firstName: {
type: Schema.Types.String,
required: true
},
lastName: {
type: Schema.Types.String,
required: true
},
password: {
type: Schema.Types.String,
required: true
},
email: {
type: Schema.Types.String,
required: true
},
cart: {
type: Schema.Types.Array
},
dateCreated: {
type: Schema.Types.Date,
default: Date.now,
required: true
},
dateUpdated: [
{
date: {
type: Schema.Types.Date
},
details: {
type: Schema.Types.ObjectId
}
}
],
verified: {
type: Schema.Types.Boolean,
required: true
},
role: {
type: Schema.Types.String,
default: ROLES_BASIC_USER
}
});
Expected Outcome
"cart" : [
{
"id" : "587b6b69799ad7ff650edbb5",
"quantity" : 1
},
{
"id" : "587b6b69799ad7ff650edbb5",
"quantity" : 1
},
{
"id" : "587b6b69799ad7ff650edbb5",
"quantity" : 1
}
],
Actual Result
"cart" : [
[
{
"id" : "587b6b69799ad7ff650edbb5",
"quantity" : 1
}
],
[
{
"id" : "587b6b69799ad7ff650edbb5",
"quantity" : 1
}
],
[
{
"id" : "587b6b69799ad7ff650edbb5",
"quantity" : 1
}
]
]

// Define User Model
const userSchema = new Schema({
firstName: {
type: Schema.Types.String,
required: true
},
lastName: {
type: Schema.Types.String,
required: true
},
password: {
type: Schema.Types.String,
required: true
},
email: {
type: Schema.Types.String,
required: true
},
cart:[ {
id: Schema.Types.ObjectId,
quantity: Number
}],
dateCreated: {
type: Schema.Types.Date,
default: Date.now,
required: true
},
dateUpdated: [
{
date: {
type: Schema.Types.Date
},
details: {
type: Schema.Types.ObjectId
}
}
],
verified: {
type: Schema.Types.Boolean,
required: true
},
role: {
type: Schema.Types.String,
default: ROLES_BASIC_USER
}
});

Try changing pushAction as follows:
const pushAction = {
$push: {
cart: { $each: [ {id: itemId, quantity: quantity } ] }
}
}
Clean existing items in cart field before trying this.
If it still fails then the issue might be with the schema.

Related

MongoDB: how can I pull an entire object from an object array that matches the Objectid?

My question is, how I can pull a team object in teams that matches the unique _id of a team(in teams array).
Here is what I have tried, however, the problem is that all entries in teams get deleted, instead of only the object that matches the team _id.
router.put("/delete/team/:id/:org", (req, res) => {
const orgid = req.params.org;
const _id = req.params.id;
Organization.findOneAndUpdate(
{
_id: orgid,
},
{
$pull: {
teams: { _id: _id },
},
},
{ multi: true }
)
.then((organization) => {
res.status(200).json(organization);
})
.catch((err) => {
res.status(400).json(err);
});
});
Each Organization in OrganizationSchema has a unique _id.
Also, each team(object) in teams array has a unique _id.
const OrganizationSchema = new Schema({
owner: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
},
register_date: {
type: Date,
default: Date.now,
},
teams: [
{
sport: {
type: String,
required: false,
},
access_code: {
type: Number,
required: false,
},
admin: [{ type: mongoose.Schema.Types.ObjectId, ref: "User" }],
events: [
{
date_time: {
type: Date,
offset: true,
},
opponent: {
type: String,
required: true,
},
home_away: {
type: String,
required: true,
},
expected_attendance: {
type: Number,
},
people_attending: [
{ type: mongoose.Schema.Types.ObjectId, ref: "User" },
],
amenities: [String],
},
],
},
],
});
The probable reason for the output you are getting is, because you are matching an ObjectId with a string.
You need to convert your string to an object.
You can do this by adding,
const ObjectID = require('mongodb').ObjectID
And then,
$pull: {
teams: { _id: new ObjectID(_id) },
},

How can I push a new object and simultaneously items to an array of that same object in MongoDB?

The problem is that I only can push the object itself, however, it won't let me push new elements to the array of the object that I want to push at the same time.
Is there another way to do it, inserting another $push to append the array, inside the object $push, doesn't work. The array "amenities" of the new object stays empty.
enter code here
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
const OrganizationSchema = new Schema({
logo: {
type: String,
required: true,
},
name: {
type: String,
required: true,
unique: true,
},
admin_email: {
type: String,
required: true,
unique: true,
},
owner: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
},
register_date: {
type: Date,
default: Date.now,
},
teams: [
{
sport: {
type: String,
required: false,
},
access_code: {
type: Number,
required: false,
},
admin: {
type: String,
required: false,
},
events: [
{
date_time: {
type: Date,
},
opponent: {
type: String,
required: true,
},
expected_attendance: {
type: Number,
},
amenities: [String],
},
],
},
],
});
module.exports = Organization = mongoose.model(
"organization",
OrganizationSchema
);
router.put("/create/event/:teamid", (req, res) => {
const teamid = req.params.teamid;
const { date_time, competitor, amenities } = req.body;
Organization.findOneAndUpdate(
{ "teams._id": teamid },
{
$push: {
"teams.$.events": {
competitor: competitor,
date_time: date_time,
$push: {
amenities: {
amenities,
},
},
},
},
}
)
.then((list) => {
res.status(200).json(list);
console.log(JSON.stringify(list));
})
.catch((err) => {
res.status(400).json(err);
});
});

Trying to get data from Mongo DB with aggregate

I have "Offers" and "Requests" collections, I need to get all offers that user made, group them by requests and find the lowest "Offer.price" on each request, each offer has requestId field.
I am using aggregate to solve this,
db.Offer.aggregate([{
$match: {
ownerId: mongoose.Types.ObjectId(req.params.ownerId)
}
},
{
$group: {
_id: "$requestId",
price: {
$min: "$price"
}
}
}
])
and This is what i get :
[ { _id: 5dc47241af1406031489c65c, price: 14 },
{ _id: 5dc47241af1406031489c653, price: 3 },
{ _id: 5dc47241af1406031489c656, price: 5 },
{ _id: 5dc8add63f73953ff408f962, price: 6 },
{ _id: 5dc8add63f73953ff408f969, price: 22 },
{ _id: 5dc47241af1406031489c658, price: 1 } ]
Now I want to populate these with rest of the data from "Offer"
const OfferSchema = new Schema({
requestId: {
type: Schema.Types.ObjectId,
ref: 'Request'
},
ownerId: {
type: Schema.Types.ObjectId,
required: true,
ref: 'User'
},
price: {
type: Number,
required: true
},
createdAt: {
type: Date,
default: Date.now
},
isBest: {
type: Boolean,
default: false
},
isWinner: {
type: Boolean,
default: false,
}
});
What would be best way to do something like this?
Thank you for your help!
Consider the following dataset:
db.dum.insert({ownerId:1, requestId:'a', price:3, createdAt:3, isWinner:true})
db.dum.insert({ownerId:1, requestId:'a', price:1, createdAt:1, isWinner:false})
db.dum.insert({ownerId:1, requestId:'a', price:2, createdAt:2, isWinner:true})
db.dum.insert({ownerId:1, requestId:'b', price:4, createdAt:2, isWinner:true})
db.dum.insert({ownerId:1, requestId:'b', price:5, createdAt:1, isWinner:false})
db.dum.insert({ownerId:2, requestId:'b', price:5, createdAt:1, isWinner:false})
You could use $reduce
Here, for a grouping id, we keep all matching documents as an array (candidates).
On the project stage, for each group we iterate through the array, and reduce it to the minimal element found (by price that is)
db.dum.aggregate([{
$match: {
ownerId: 1
}
},
{
$group: {
_id: "$requestId",
candidates: { $push:'$$ROOT'}
}
},
{
$project:{
item: {
$reduce: {
input: '$candidates',
initialValue: '$candidates.0',
in: {
$cond: {
if: {
$lt: ['$$value.price', '$$this.price']
},
then:'$$value',
else:'$$this'
}
}
}
}
}
},
{
$replaceRoot:{newRoot:'$item'}
}
]).toArray()
output:
[
{
"_id" : ObjectId("5ddcc8e0eb1f0217802fb507"),
"ownerId" : 1,
"requestId" : "b",
"price" : 4,
"createdAt" : 2,
"isWinner" : true
},
{
"_id" : ObjectId("5ddcc8e0eb1f0217802fb505"),
"ownerId" : 1,
"requestId" : "a",
"price" : 1,
"createdAt" : 1,
"isWinner" : false
}
]

Mongodb aggregate run in terminal but when run in node.js with mongoose it's return "APIError: Arguments must be aggregate pipeline operators"

When I run the aggregate in the mongo throw terminal it returns the correct data, but when I run the same aggregate in the code it returns an error.
I had tried to use just match and group to test, but I still get the same error even if I used mongoose functions but without any luck
I plan to try to improve my aggregate game but for now, I need this to run and I'm stuck here for the last couple of days.
"APIError: Arguments must be aggregate pipeline operators\n at new ExtendableError (/vagrant/server/helpers/APIError.js:15:11)\n at new APIError (/vagrant/server/helpers/APIError.js:31:5)\n at app.use (/vagrant/config/express.js:64:22)\n at Layer.handle_error (/vagrant/node_modules/express/lib/router/layer.js:71:5)\n at trim_prefix (/vagrant/node_modules/express/lib/router/index.js:315:13)\n at /vagrant/node_modules/express/lib/router/index.js:284:7\n at Function.process_params (/vagrant/node_modules/express/lib/router/index.js:335:12)\n at next (/vagrant/node_modules/express/lib/router/index.js:275:10)\n at /vagrant/node_modules/express/lib/router/index.js:635:15\n at Immediate.next (/vagrant/node_modules/express/lib/router/index.js:260:14)\n at Immediate._onImmediate (/vagrant/node_modules/express/lib/router/index.js:635:15)\n at runCallback (timers.js:706:11)\n at tryOnImmediate (timers.js:676:5)\n at processImmediate (timers.js:658:5)"
customer model
const mongoose = require('mongoose');
const customerContactsSchema = require('./customerContacts.model');
const appSettings = require('../../config/appSettings');
/**
* Customer Schema
*/
const CustomerSchema = new mongoose.Schema({
avatar: {
type: String,
required: false
},
firstName: {
type: String,
required: false
},
lastName: {
type: String,
required: false
},
password: {
type: String,
required: false,
select: false
},
address: {
country: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Country',
required: false
},
city: {
type: mongoose.Schema.Types.ObjectId,
ref: 'City',
required: false
},
},
language: {
type: String,
required: true,
enum: appSettings.languages,
default: 'english'
},
mobileNumber: {
number: {
type: String,
required: true,
trim: true
},
country: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Country',
required: true
}
},
email: {
type: String,
lowercase: true,
trim: true,
required: false
},
birthday: {
type: Date,
required: false
},
gender: {
type: String,
enum: ['male', 'female'],
required: false
},
friends: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'Customer',
required: false
}],
contacts: [customerContactsSchema],
favourites: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'Company',
required: false
}],
transactions: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'CustomerTransaction',
required: false
}],
googleId: {
type: String
},
facebookId: {
type: String
},
twitterId: {
type: String
},
fcmToken: {
type: String
},
otpToken: {
type: Number,
required: false,
default: 1234
},
otpTokenExpiration: {
type: Date,
required: false
},
status: {
type: String,
enum: ['active', 'pending', 'suspended', 'blocked', 'deleted'],
required: true,
default: 'pending'
}
}, { timestamps: true });
/**
* #typedef Customer
*/
module.exports = mongoose.model('Customer', CustomerSchema);
CustomerTransaction model
const mongoose = require('mongoose');
const locationSchema = require('./location.model');
/**
* CustomerTransaction Schema
*/
const CustomerTransactionSchema = new mongoose.Schema({
company: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Company',
required: true
},
customer: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Customer',
required: true
},
type: {
type: String,
// debit - , credit +
enum: ['debit', 'credit'],
required: true
},
expirationDate: {
type: Date,
required: false
},
point: {
amount: {
type: Number,
required: false
},
value: {
type: Number,
required: false
}
},
closeValue: {
type: Number,
required: false
},
closePoints: {
type: Number,
required: false
},
offer: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Offer',
required: false
},
voucher: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Voucher',
required: false
},
stamp: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Stamp',
required: false
},
punches: {
type: Number,
required: false
},
date: {
type: Date,
required: false,
default: Date.now()
},
gift: {
type: Boolean,
default: false
},
giftSender: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Customer',
required: false
},
giftReceiver: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Customer',
required: false
},
status: {
type: String,
enum: ['active', 'used', 'sent', 'expired', 'deleted'],
required: true,
default: 'active'
},
location: locationSchema
}, { timestamps: true });
/**
* #typedef CustomerTransaction
*/
module.exports = mongoose.model('CustomerTransaction', CustomerTransactionSchema);
aggregate
db.getCollection('customers').aggregate([
{
$match: {
gender: new RegExp(`.*${'' || ''}.*`, 'i'),
birthday: {
$gte: ISODate('1991-04-29T00:00:00.000Z'),
$lte: ISODate('2019-04-29T00:00:00.000Z'),
},
createdAt: {
$gte: ISODate('1991-04-29T00:00:00.000Z'),
$lte: ISODate('2019-04-29T00:00:00.000Z'),
},
}
},
{
$lookup: {
from: 'customertransactions',
localField: '_id',
foreignField: 'customer',
as: 'CustomerTransaction'
}
},
{
$match: {
'CustomerTransaction.company': ObjectId('5cd2af3a5bfc5b1a40b7de49'),
}
},
{
$project: {
firstName: 1,
lastName: 1,
CustomerTransaction: 1,
CustomerStampTransaction: {
$setDifference: [
{
$map: {
input: '$CustomerTransaction',
as: 'el',
in: {
$cond: [
{
$and: [
{ $gte: ['$$el.stamp', 1] },
{ $lt: ['$$el.offer', 0] },
{ $lt: ['$$el.voucher', 0] },
{ $eq: ['$$el.company', ObjectId('5cd2af3a5bfc5b1a40b7de49')] }
]
},
{
_id: '$$el._id'
},
false
]
}
}
},
[false]
]
},
CustomerPointTransaction: {
$setDifference: [
{
$map: {
input: '$CustomerTransaction',
as: 'el',
in: {
$cond: [
{
$and: [
{ $lt: ['$$el.stamp', 0] },
{ $lt: ['$$el.offer', 0] },
{ $lt: ['$$el.voucher', 0] },
{ $eq: ['$$el.company', ObjectId('5cd2af3a5bfc5b1a40b7de49')] }
]
},
{
_id: '$$el._id',
createdAt: '$$el.createdAt',
closePoints: '$$el.closePoints',
},
false
]
}
}
},
[false]
]
}
}
},
{
$project: {
firstName: 1,
lastName: 1,
CustomerPointTransaction: 1,
stampCount: {
$cond: {
if: { $isArray: '$CustomerStampTransaction' },
then: { $size: '$CustomerStampTransaction' },
else: 0
}
},
//"CustomerTransaction": "$CustomerTransaction"
}
},
{
$unwind: {
path: '$CustomerPointTransaction',
preserveNullAndEmptyArrays: true
}
},
{ $sort: { 'CustomerPointTransaction.createdAt': 1 } },
{
$group: {
_id: '$_id',
firstName: { $first: '$firstName' },
lastName: { $first: '$lastName' },
stampCount: { $first: '$stampCount' },
CustomerPointTransaction: { $last: '$CustomerPointTransaction' }
}
},
{
$group: {
_id: '$_id',
firstName: { $first: '$firstName' },
lastName: { $first: '$lastName' },
stampCount: { $first: '$stampCount' },
pointCount: { $first: '$CustomerPointTransaction.closePoints' }
}
},
]);
aggregate result
{ "_id" : ObjectId("5c8918ab9979f345214158ba"), "firstName" : "mohammed", "lastName" : "alqahtani", "stampCount" : 1, "pointCount" : null }
{ "_id" : ObjectId("5cc161039f11d13109185b15"), "firstName" : "Mohammedaadsxxxa", "lastName" : "AlQahtaniss", "stampCount" : 0, "pointCount" : 9 }
code
function search(req, res) {
Customer.aggregate([
{
$match: {
gender: new RegExp(`.*${'' || ''}.*`, 'i'),
}
},
{
$lookup: {
from: 'customertransactions',
localField: '_id',
foreignField: 'customer',
as: 'CustomerTransaction'
}
},
{
$match: {
'CustomerTransaction.company': req.company._id,
}
},
{
$project: {
firstName: 1,
lastName: 1,
CustomerTransaction: 1,
CustomerStampTransaction: {
$setDifference: [
{
$map: {
input: '$CustomerTransaction',
as: 'el',
in: {
$cond: [
{ $and: [
{ $gte: ['$$el.stamp', 1] },
{ $lt: ['$$el.offer', 0] },
{ $lt: ['$$el.voucher', 0] },
{ $eq: ['$$el.company', req.company._id] }
] },
{
_id: '$$el._id'
},
false
]
}
}
},
[false]
]
},
CustomerPointTransaction: {
$setDifference: [
{
$map: {
input: '$CustomerTransaction',
as: 'el',
in: {
$cond: [
{ $and: [
{ $lt: ['$$el.stamp', 0] },
{ $lt: ['$$el.offer', 0] },
{ $lt: ['$$el.voucher', 0] },
{ $eq: ['$$el.company', req.company._id] }
] },
{
_id: '$$el._id',
createdAt: '$$el.createdAt',
closePoints: '$$el.closePoints',
},
false
]
}
}
},
[false]
]
}
}
},
{
$project: {
firstName: 1,
lastName: 1,
CustomerPointTransaction: 1,
stampCount: { $cond: { if: { $isArray: '$CustomerStampTransaction' }, then: { $size: '$CustomerStampTransaction' }, else: 0 } },
//"CustomerTransaction": "$CustomerTransaction"
}
},
{
$unwind: {
path: '$CustomerPointTransaction',
preserveNullAndEmptyArrays: true
}
},
{ $sort: { 'CustomerPointTransaction.createdAt': 1 } },
{
$group: {
_id: '$_id',
firstName: { $first: '$firstName' },
lastName: { $first: '$lastName' },
stampCount: { $first: '$stampCount' },
CustomerPointTransaction: { $last: '$CustomerPointTransaction' }
}
},
{
$group: {
_id: '$_id',
firstName: { $first: '$firstName' },
lastName: { $first: '$lastName' },
stampCount: { $first: '$stampCount' },
pointCount: { $first: '$CustomerPointTransaction.closePoints' }
}
},
], { cursor: {} })
.then((customers) => {
console.log('kkkkkkkkk');
if (customers) {
res.json(Response.success(customers));
} else {
res.json(Response.success('klklk'));
}
})
.catch(err => res.status(httpStatus.INTERNAL_SERVER_ERROR)
.json(Response.failure(err)));
}
keep in mind even if I run the code like the following it's still throwing the same error
function search(req, res) {
Customer.aggregate([
{
$match: {
gender: new RegExp(`.*${'' || ''}.*`, 'i'),
}
}
], { cursor: {} })
.then((customers) => {
console.log('kkkkkkkkk');
if (customers) {
res.json(Response.success(customers));
} else {
res.json(Response.success('klklk'));
}
})
.catch(err => res.status(httpStatus.INTERNAL_SERVER_ERROR)
.json(Response.failure(err)));
}
Customer.aggregate([
{
$match: {
gender: new RegExp(`.*${'' || ''}.*`, 'i'),
}
}
], { cursor: {} }) // ERROR IS HERE
With mongoose and .aggregate, there is no option parameter like how it is on .find. Instead try this
Customer.aggregate([
{
$match: {
gender: new RegExp(`.*${'' || ''}.*`, 'i'),
}
}
]).cursor()
UPDATED
Customer.aggregate([
{
$match: {
gender: new RegExp(`.*${'' || ''}.*`, 'i'),
}
}
]).exec((error, docs) => {
console.log(docs)
res.json(docs)
})
will this worked with me
async function search(req, res) {
try {
const customers = await Customer.aggregate([
{
$match: {
gender: 'male'
}
}
]
)
.cursor({})
.exec();
customers.get(function(err, ress){
if (ress) {
res.json(Response.success(ress));
} else {
res.json(Response.success('klklk'));
}
});
} catch(e) {
res.status(httpStatus.INTERNAL_SERVER_ERROR) .json(Response.failure(e));
}
}
but I could not figure out why it's working and what was wrong with my previous code

Mongoose - for each get count and select

I have two schemas Advert:
var AdvertSchema = new Schema({
created: {
type: Date,
default: Date.now
},
title: {
type: String,
default: '',
trim: true,
required: 'title cannot be empty'
},
advert: {
type: String,
default: '',
required: 'advert cannot be empty'
},
sport: [{
type: Schema.ObjectId,
ref: 'Sport'
}],
});
mongoose.model('Advert', AdvertSchema);
and Sport:
var SportSchema = new Schema({
sportName: {
type: String,
default: '',
required: 'sport cannot be empty'
}
});
mongoose.model('Sport', SportSchema);
And I would like to know how can I for each sport get count of adverts and select _id and sportName of Sport schema. Note that in Advert schema is sport as array. The result then should be something like this:
[{
_idOfSport: "some ID",
sportName: "sport name",
countOfSportAdverts: "number"
},
etc for other sports...
]
I guess I can use aggregation framework but I don't know how. Is it somehow possible to do it?
I suppose this could do the trick:
Sport.find({}, function(err, sports) {
var results = [], sportMap = {};
sports.forEach(function(sport) {
sportMap["_idOfSport"] = sport._id;
sportMap["sportName"] = sport.sportName;
Advert.aggregate([
{
$match: {
sport: sport._id
}
},
{
$group: {
_id: null,
count: { $sum: 1 }
}
}
], function(err, res){
sportMap["countOfSportAdverts"] = res[0].count;
results.push(sportMap);
});
});
});

Categories

Resources