How do I access `findAndModify` method from Mongo in Node (with Mongoose)? - node.js

This is /models/joke.js:
var mongoose = require ('mongoose')
, database = mongoose.connect('localhost', 'joke', { server: { poolSize: 3 } });
var jokeSchema = mongoose.Schema({
content: String,
upvotes: {
type: Number,
default: 0
},
downvotes: {
type: Number,
default: 0
},
views: {
type: Number,
default: 0
},
published: {
type: Boolean,
default: true
},
author_id: Number,
created: {
type: Date,
default: Date.now
}
});
var Joke = mongoose.model('Joke', jokeSchema);
module.exports = Joke;
And I'm doing this to check if something exist — if doesn't, then create:
var Joke = require ('./models/joke');
// ...
Joke.findAndModify({
query: {
content: content
},
update: {
$setOnInsert: {
content: "test",
}
},
new: true,
upsert: true
});
But my console shout me the following:
TypeError: Object function model(doc, fields, skipId) {
if (!(this instanceof model))
return new model(doc, fields, skipId);
Model.call(this, doc, fields, skipId);
} has no method 'findAndModify'
I can understand the reason for the error – I'm calling it through a model instead of a collection, but how do I access my Jokes' collection methods?
I mean, all of the examples were using db.collection.findAndModify, but what is this db.collection? How do I call it?

To access findAndModify update functionality from Mongoose, use findOneAndUpdate:
Joke.findOneAndUpdate(
{ content: content },
{ $setOnInsert: { content: "test" } },
{ new: true, upsert: true },
callback
);

Related

how to insert into deeply nested element API nodejs mongoose

I have 4 nested documents as follow:
//Nested sub document subControl
const SubControlSchema = new Schema({
subControlNo: {
type: String
},
updated: Date,
created: {
type: Date,
default: Date.now
}
});
//Nested sub document control
const ControlSubSchema = new Schema({
mainControl: {
type: String
},
subControls: [SubControlSchema],
controlDescription: {
type: String,
trim: true
},
updated: Date,
created: {
type: Date,
default: Date.now
}
});
//Nested sub document domain
const DomainSubSchema = new Schema({
_id: {
type: Schema.ObjectId,
auto: true
},
domainNo: {
type: String,
trim: true
},
domainName: {
type: String,
trim: true
},
domainDescription: {
type: String,
trim: true
},
controls: [ControlSubSchema],
updated: Date,
created: {
type: Date,
default: Date.now
}
});
// framework Schema
const FrameworkSchema = new Schema({
name: {
type: String,
trim: true
},
description: {
type: String,
trim: true
},
regulator: {
type: Schema.Types.ObjectId,
ref: 'Regulator',
default: null
},
client: {
type: Schema.Types.ObjectId,
ref: 'Client',
default: null
},
domains: [DomainSubSchema],
updated: Date,
created: {
type: Date,
default: Date.now
}
});
module.exports = Mongoose.model('Framework', FrameworkSchema);
I'm trying to post a control under the domain which is inside the framework, here's what I have been trying to do:
//Add new control under a specific domain and framework
router.post('/add/:frameworkId/:domainId', auth, async (req, res) => {
try {
const control = req.body.controls; //take the request from the body
const query = { _id: req.params.frameworkId, _id: req.params.domainId };//pushing into the framework model by taking the ID from URL
await Framework.updateOne(query, { $push: { domains: control } }).exec(); //push the query into the framework model
res.status(200).json({
success: true,
controls: control
});
} catch (error) {
res.status(400).json({
// error: 'Your request could not be processed. Please try again.'
error
});
}
});
Data posted in postman:
Link: http://localhost:3000/api/framework/add/6233277f411377367f8ad1c0/6233277f411377367f8ad1c1
{
"controls":
{
"mainControl": "1-5",
"subControls": [{
"subControlNo": "1-4-1"
},
{
"subControlNo": "1-4-2"
}],
"controlDescription": "controlDescriptionTest"
}
}
Response:
{
"success": true,
"controls": {
"mainControl": "1-5",
"subControls": [
{
"subControlNo": "1-4-1"
},
{
"subControlNo": "1-4-2"
}
],
"controlDescription": "controlDescriptionTest"
}
}
Problem: I'm not getting any new data in mongodb , any idea if I'm approaching this the correct way? I'm guessing the data is posted correctly and It's a problem with saving it to the database
Picture of my schema: I want to be able to add elements under the controls:
First if you want your code to insert and not update you should use insertOne and not updateOne, regarding an "update" operation I can see 2 potential "issues" here:
req.params.frameworkId and req.params.domainId come as string type. And I assume the _id field is type ObjectId and not string.
To fix this you just need to cast it to the proper type, like so:
import { ObjectId } from 'mongodb';
...
{ _id: new ObjectId(req.params.frameworkId) }
Both parameters are "querying" the same field (_id), unless this is intentional somehow if these values are different it will never find a document to match, this should be changed.
Lastly if you want to update an existing object if exists, and if not insert then you should use updateOne with the upsert option:
await Framework.updateOne(query, { $push: { domains: control } }, { upsert: true }).exec();

Mongoose query not returning updated information with { new: true } param

Why is my query not returning updated information?
UserSchema.findByIdAndUpdate(
{ _id: userId },
{ $set: { couponList: couponList } }, { new: true }).populate('couponList').exec().then(user => {
// user returning with the old information
}).catch(err => console.log(err));
I have 3 params:
first one is the id of the user i want to update (objectId)
second one is the information I want to update (objectId Array)
third is the flag that says I want to receive the updated information (Boolean)
My coupon schema goes like this:
import mongoose from 'mongoose';
const CouponSchema = new mongoose.Schema({
name: {
type: String,
default: 'Unknown'
},
description: {
type: String,
default: undefined
},
validity: {
type: Date,
default: null
},
code: {
type: String,
default: undefined
},
blackList: {
type: Array,
ref: 'user',
default: []
},
blackListFlag: {
type: Boolean,
default: false,
},
whiteList: {
type: Array,
ref: 'user',
default: []
},
limit: {
type: Number,
default: 0,
},
counter: {
type: Number,
default: 0,
},
amount: {
type: Number,
default: 0,
},
discountType: {
type: String,
default: undefined,
}
}, { collection: 'coupon' });
export default mongoose.model('coupon', CouponSchema);
And in my user schema I have a ref to the coupon schema:
couponList : {
type: Array,
ref: 'coupon',
default: []
},
I think you need to define the field couponList in your schema.
Edit: Just noticed the UserSchema, theoretically, you should be fine, if you are pushing correct objectIds.
findByIdAndUpdate with {new: true} must work as intended.
But I'm not aware of Your code totally and what kind of data You're sending as couponList.
So try to separate update and select operations and see what happens. In fact mongoose does the same when You call findByIdAndUpdate.
For example using express framework:
const User = mongoose.model('user');
router.put('/user/:userId/coupons', async (req, res) => {
try {
const {userId} = req.params;
const {couponList} = req.body;
await User.updateOne(
{_id: userId},
{$set: {couponList: couponList}},
{upsert: false}
);
const user = await User
.findById(userId)
.populate('couponList').lean();
res.status(200).send(user);
}
catch (error) {
console.log(error);
res.status(500).send({})
}
});
P.S. Only reason for that unexpected behavior may be that somehow (but it's not possible) it uses native driver for which {new: true} must be written as: {returnNewDocument: true}
Check this link
I found out that the problem was not with returning updated information but it was on populating the collection.
The correct reference to the coupon collection in user schema:
couponList: [ { type: mongoose.Schema.ObjectId, ref: 'coupon' } ],

Mongoose Virtual field with async getter

I have a item model where it a virtual field to refer stock badges.
'use strict';
const mongoose = require('mongoose');
const mongooseHidden = require('mongoose-hidden')();
const Badge = mongoose.model('Badge');
const validateProperty = function(property) {
return (property.length);
};
const Schema = mongoose.Schema;
const ItemSchema = new Schema({
itemCode: {
type: Number,
index: {
unique: true,
sparse: true // For this to work on a previously indexed field, the index must be dropped & the application restarted.
},
required: true
},
itemName: {
type: String,
uppercase: true,
trim: true
},
barcode: {
type: String,
trim: true
},
category: {
type: Schema.Types.ObjectId,
ref: 'Category'
},
subCategory: {
type: Schema.Types.ObjectId,
ref: 'SubCategory'
},
updated: {
type: Date
},
created: {
type: Date,
default: Date.now
},
status: {
type: String,
enum: [
'active', 'inactive', 'removed'
],
default: 'active'
}
}, {id: false});
ItemSchema.virtual('badges').get(function() {
return this.getAvailableBadges();
});
ItemSchema.methods.getAvailableBadges = function() {
Badge.find({
item: this._id
}, (err, badges) => {
if (badges) {
return badges;
} else {
return [];
}
});
};
ItemSchema.set('toJSON', {virtuals: true});
ItemSchema.set('toObject', {virtuals: true});
ItemSchema.plugin(mongooseHidden, {
hidden: {
_id: false,
__v: true
}
});
mongoose.model('Item', ItemSchema);
And batch model as below
'use strict';
const mongoose = require('mongoose');
const mongooseHidden = require('mongoose-hidden')();
const validateProperty = function(property) {
return (property.length);
};
const Schema = mongoose.Schema;
const BadgeSchema = new Schema({
item: {
type: Schema.Types.ObjectId,
ref: 'Item'
},
qty: {
type: Number,
validate: [validateProperty, 'Please enter Quantity !']
},
purchasingPrice: {
type: Number,
validate: [validateProperty, 'Please enter purchasingPrice !']
},
sellingPrice: {
type: Number,
validate: [validateProperty, 'Please enter sellingPrice !']
},
updated: {
type: Date
},
created: {
type: Date,
default: Date.now
},
status: {
type: String,
enum: [
'active', 'inactive', 'removed'
],
default: 'active'
}
});
BadgeSchema.plugin(mongooseHidden, {
hidden: {
_id: false,
__v: true
}
});
mongoose.model('Badge', BadgeSchema);
Item's badge virtual field doesn't got populated.
How are we going to work with async getter method
I have put some console log statements and found that getAvailableBadges is getting data.
I need to send json object with virtual field values via express. How to I do it?
What I did was create an virtual property
ItemSchema.virtual('badges', {
ref: 'Badge',
localField: '_id',
foreignField: 'item'
});
And populate it with
{
path: 'badges',
select: [
'qty', 'purchasingPrice', 'sellingPrice'
],
options: {
sort: {
'created': -1
}
}
}
Well, the operations are asynchronous so you have to wait for the callback to fire.
You can only return the values by passing it in the callback (or you can set the values of the current object prior to calling the callback).
I think it would be something like this:
ItemSchema.virtual('badges').get(function (callback) {
Badge.find({ item: this._id }, callback);
};
Then you would use it like
item.badges(function (err, badges) {
// do something with badges
});

Mongoose Schema.update doesn't update boolean

I have tried updating other fields and it works just fine.
The command I am using in my API:
User.update({ email: targetUser.email }, { $set: { isAdmin: true }, $push: { 'log.updated': new Date() } }, function (err, user) {
if (err) {
responseObject.err = err;
responseObject.data = null;
responseObject.code = 422;
return res.json(responseObject);
}
return res.json(responseObject);
});
To clarify, when I try to run this, the API returns a code 200, meaning everything worked fine, but when I check the database the isAdmin value wasn't changed.
Any suggestions would be helpful, running out of ideas here!
User Schema as requested:
var UserSchema = new Schema({
name: { type: String, default: "", index: 'text' },
email: { type: String, lowercase: true },
role: { type: String, default: "" },
meta: {
skills: { type: Array, default: [], index: 'text' },
about: { type: String, default: "", index: 'text' },
education: { type: Array, default: [], index: 'text' },
location: {
address: {
a: { type: String, default: "" },
p: { type: String, default: "" },
c: { type: String, default: "" }
},
geo: {
lat: { type: Number, default: 0 },
lng: { type: Number, default: 0 }
}
}
},
compMeta:
{
departments: { type: Array, default: [], index: 'text' },
employees:
[
{
emId: Number,
empName: String,
empDep: String // Dunno if i should use Dep name or Dep ID gonna look in to that later
}
],
}
,
settings: {
search: {
distance: {
n: { type: Number, default: 100 },
t: { type: String, default: "km" }
}
}
},
created: {
type: Date,
default: Date.now
},
//Rating is an array of objects that consist of rateing 0-100 , job database id , comments from the Company
rating:
[
{
rate: Number,
jobId: Number,
jobComments: String
}
],
/*rating:
{
userTotalRating: {type: Number, default: 0},
ratingCounter : {type: Number, default: 0}
}*/
sensitive: {
cpr_cvr: String,
},
stripe: { type: String },
facebook: {},
linkedin: {},
log: {
updated: { type: Array, default: [] }
},
hashedPassword: String,
provider: { type: String, default: 'local' },
salt: String
});
UPDATE:
Mongodb version: 3.0.7
Turns out I just forgot to add the isAdmin field to my User Schema! Also, my call to the update was wrong, I changed it to this:
User.update({ email: targetUser.email }, { $set: { isAdmin: true }}, { $push: { 'log.updated': new Date() } }, function (err, user) {
if (err) {
responseObject.err = err;
responseObject.data = null;
responseObject.code = 422;
return res.json(responseObject);
}
return res.json(responseObject);
});
Thanks to everyone that put an effort to help me! :)
I encountered a similar problem. The solution was to add the callback.
This doesn't work:
Ride.updateOne({driver:req.body.id},{$set:{isBusy:true}});
This works:
Ride.updateOne({driver:req.body.id},{$set:{isBusy:true}},(e,s)=>{});
Try updating two fields with $set
User.update({ email: targetUser.email }, { $set: { isAdmin: true, 'log.updated': new Date() } }, function (err, user) {
if (err) {
responseObject.err = err;
responseObject.data = null;
responseObject.code = 422;
return res.json(responseObject);
}
return res.json(responseObject);
});
Hope it's works.
There is easier way to handle the issue. As per the documentation, the second parameter is the object where you can update the statement.
A.findByIdAndUpdate(id, update, options, callback)
So you just need to take everything inside the update object.
User.update({ email: targetUser.email, $set: {isAdmin: true}} // ... etc

Mongoose: value from Model

I have the following model:
var requestSchema = new Schema({
description: { type: String, required: true },
country: { type: String, index: true },
shipping: [shipping],
deliveryLoc: { type: String, index: true },
price: { type: Number, default: 0 },
})
I now want to get the price using mongoose and I am not sure which command I have to use.
I tried:
var pricy = _.first(_.where(request.price));
and it does not work, I get undefined even through through other queries in the same file I can get "shipping".
Getting the shipping type works with the following command:
var shipping = _.first(_.where(request.shipping, { type: shippingType }));
Am I using the wrong command?
You should be able to use the select method as follows:
// find a request
var query = Request.findOne();
// selecting the `price` field
query.select('price');
// execute the query at a later time
query.exec(function (err, request) {
if (err) return handleError(err);
console.log('The price is $%s.', person.price) // The price is $6.92
});
or if passing a callback:
var Request = mongoose.model('Request', requestSchema);
// find each request with a country matching 'Zimbabwe', selecting the `price` field
Request.findOne({ 'country': 'Zimbabwe' }, 'price', function (err, request) {
if (err) return handleError(err);
console.log('The price is $%s.', request.price) // The price is $6.92.
});
First, you need to create your schema like that:
var items = new Schema({
description: { type: String, required: true },
country: { type: String, index: true },
shipping: [shipping],
deliveryLoc: { type: String, index: true },
price: { type: Number, default: 0 },
});
After that you need to compile the new schema and add it to the database:
items = mongoose.model("Items", items); // The table name will be "Items"
When the model is created, you can execute your query (find or findOne):
items.findOne({price: request.price}, function (error, item) {
if (error) {
console.log(error);
} else {
console.log(item);
}
});
The full code:
var mongoose, Schema;
mongoose = require("mongoose");
Schema = mongoose.Schema;
var items = new Schema({
description: { type: String, required: true },
country: { type: String, index: true },
shipping: [shipping],
deliveryLoc: { type: String, index: true },
price: { type: Number, default: 0 },
});
items = mongoose.model("Items", items);
items.findOne({price: request.price}, function (error, item) {
if (error) {
console.log(error);
} else {
console.log(item);
}
});

Resources