mongoosejs perform query within methods - node.js

I'm using mongoosejs and have custom methods to populate some data during pre save. Is there a way to query the db, within methods?
For example:
UserSchema.methods.createRandom = function(callback) {
var random = 123;
this.findOne({random: random}, function(err, doc) {
if (!doc) return callback(random);
this.createRandom(callback);
});
}
UserSchema.pre('save', function(next) {
this.createRandom(function(random) {
this.random = random;
next();
});
}
This is basically what I'm trying to acheive, but this in methods does not reference the model, it references the object to be saved. Anyway to access the model for the findOne().
Thanks!

It's a bit cryptic and I don't know if it's documented anywhere, but I've reliably done this in the past by accessing an instance's model via its constructor property:
this.constructor.findOne({random: random}, function(err, doc) {

Related

Limit posted fields for insert

I'm trying to limit the fields a user can post when inserting an object in mongodb. I know ho i can enforce fields to be filled but I can't seem to find how to people from inserting fields that I don't want.
This is the code I have now for inserting an item.
app.post("/obj", function (req, res) {
var newObj = req.body;
//TODO filter fields I don't want ?
if (!(newObj .id || newObj .type)) {
handleError(res, "Invalid input", "Must provide a id and type.", 400);
return;
}
db.collection(OBJ_COLLECTION).insertOne(newObj, function(err, doc) {
if (err) {
handleError(res, err.message, "Failed to create new object.");
} else {
res.status(201).json(doc.ops[0]);
}
});
});
There's likely JS native ways to do this, but I tend to use Lodash as my toolbox for most projects, and in that case what I normally do is setup a whitelist of allowed fields, and then extract only those from the posted values like so:
const _ = require('lodash');
app.post("/obj", function (req, res) {
var newObj = _.pick(req.body, ['id', 'type','allowedField1','allowedField2']);
This is pretty straightforward, and I usually also define the whitelist somewhere else for reuse (e.g. on the model or the like).
As a side note, I avoid using 'id' as a field that someone can post to for new objects, unless I really need to, to avoid confusion with the autogenerated _id field.
Also, you should really look into mongoose rather than using the straight mongodb driver, if you want to have more model-based control of your documents. Among other things, it will strip any fields off the object if they're not defined in the schema. I still use the _.pick() method when there are things that are defined in the schema, but I don't want people to change in a particular controller method.

Why can't I seem to merge a normal Object into a Mongo Document?

I have a data feed from a 3rd party server that I am pulling in and converting to JSON. The data feed will never have my mongoDB's auto-generated _ids in it, but there is a unique identifier called vehicle_id.
The function below is what is handling taking the data-feed generated json object fresh_event_obj and copying its values into a mongo document if there is a mongo document with the same vehicle_id.
function update_vehicle(fresh_event_obj) {
console.log("Updating Vehicle " + fresh_event_obj.vehicleID + "...");
Vehicle.find({ vehicleID: fresh_event_obj.vehicleID }, function (err, event_obj) {
if (err) {
handle_error(err);
} else {
var updated = _.merge(event_obj[0], fresh_event_obj);
updated.save(function (err) {
if (err) {
handle_error(err)
} else {
console.log("Vehicle Updated");
}
});
}
});
}
The structures of event_obj[0] and fresh_event_obj are identical, except that event_obj[0] has _id and __v while the "normal" object doesn't.
When I run _.merge on these two, or even my own recursive function that just copies values from the latter to the former, nothing in the updated object is different from the event_obj[0], despite fresh_event_obj having all new values.
Does anyone have any idea what I'm doing wrong? I feel it is obvious and I'm just failing to see it.
The problem is that if you don't have properties defined in your schema, and if they don't already exist, you can't create them with
doc.prop = value
even if you have {strict:false} in your schema.
The only way to set new properties is to do
doc.set('prop', value)
(You still have to have {strict:false} in your schema if that property doesn't exist in your schema)
As for having too many properties to be defined in schema, you can always use for-in loop to go through object properties
for(key in fresh_event_obj)
event_obj.set(key, fresh_event_obj[key]);

Does MongooseJS return a new fresh result object on save()?

By default MongoDB on collection.save() returns a WriteResult object as stated in the documentation:
The save() returns a WriteResult object that contains the status of the insert or update operation.
But with Mongoose (and I guess the underlying mongodb driver in node) you can add a second parameter that is populated with the entire object that you just inserted and with the new _id:
var user = new User(req.body);
user.save(function (err, userResult) {
if (err) {
log.error(err);
}
log.debug('User data: ', userResult);
});
So my question:
Does userResult contain retrieved data from Mongo and it's a fresh object OR is the object passed already passed into the save() method and from the database call is merged with only some partial data like the generated _id and/or created date?
If you take a look at Model.prototype.save():
https://github.com/Automattic/mongoose/blob/8cb0e35/lib/model.js#L254
It looks like you get back the same model instance (self) in your userResult.
To answer your question, the object returned is a new object -- not the original one (so user != userResult)
Mongoose supports a few options -- new: true and lean: false settings may be of interest to you to modify how data is returned in different operations (if you want it to be a new option or do not care).
You can verify this 'new object' is the case by using the following:
var user = new User(req.body);
user.save(function (err, userResult) {
if (err) {
log.error(err);
}
user.name = 'TEST';
console.log(`${userResult.name} will be undefined but ${user.name} will not be`);
log.debug('User data: ', userResult);
});

What is the difference between methods and statics in Mongoose?

What is the difference between methods and statics?
Mongoose API defines statics as
Statics are pretty much the same as methods but allow for defining functions that exist directly on your Model.
What exactly does it mean? What does existing directly on models mean?
Statics code example from documentation:
AnimalSchema.statics.search = function search (name, cb) {
return this.where('name', new RegExp(name, 'i')).exec(cb);
}
Animal.search('Rover', function (err) {
if (err) ...
})
Think of a static like an "override" of an "existing" method. So pretty much directly from searchable documentation:
AnimalSchema.statics.search = function search (name, cb) {
return this.where('name', new RegExp(name, 'i')).exec(cb);
}
Animal.search('Rover', function (err) {
if (err) ...
})
And this basically puts a different signature on a "global" method, but is only applied when called for this particular model.
Hope that clears things up a bit more.
It seems like
'method' adds an instance method to documents constructed from Models
whereas
'static' adds static "class" methods to the Model itself
From the documentation:
Schema#method(method, [fn])
Adds an instance method to documents constructed from Models compiled from this schema.
var schema = kittySchema = new Schema(..);
schema.method('meow', function () {
console.log('meeeeeoooooooooooow');
})
Schema#static(name, fn)
Adds static "class" methods to Models compiled from this schema.
var schema = new Schema(..);
schema.static('findByName', function (name, callback) {
return this.find({ name: name }, callback);
});

Mongoose: Is there a way to default lean to true (always on)?

I have a read-only API that I'd like Mongoose to always have lean queries for.
Can I enable this either on a schema or connection level to be true by default?
The easiest way is to monkey patch mongoose.Query class to add default lean option:
var __setOptions = mongoose.Query.prototype.setOptions;
mongoose.Query.prototype.setOptions = function(options, overwrite) {
__setOptions.apply(this, arguments);
if (this.options.lean == null) this.options.lean = true;
return this;
};
Mongoose creates new instance of mongoose.Query for every query and setOptions call is a part of mongoose.Query construction.
By patching mongoose.Query class you'll be able to turn lean queries on globally. So you won't need to path all mongoose methods (find, findOne, findById, findOneAndUpdate, etc.).
Mongoose uses Query class for inner calls like populate. It passes original Query options to each sub-query, so there should be no problems, but be careful with this solution anyway.
A hack-around could be performed something like this:
Current way of executing the query:
Model.find().lean().exec(function (err, docs) {
docs[0] instanceof mongoose.Document // false
});
Fiddle with the Model's find method:
var findOriginal = Model.prototype.find;
Model.prototype.find = function() {
return findOriginal.apply(this, arguments).lean();
}
New way of executing the query:
Model.find().exec(function (err, docs) {
docs[0] instanceof mongoose.Document // false
});
I have not tested this code, but if you have tried to override library functionality in JavaScript before, you will easily grasp where I'm getting
I have to use My nestjs Application like this. Its perfectly working for me.
import { Query } from 'mongoose'
Query.prototype.setOptions = function(options: any) {
__setOptions.apply(this, arguments)
if (!this.mongooseOptions().lean) this.mongooseOptions().lean = true
return this
}
Refer to the above comments:
mongoose.Query.prototype.setOptions = function(options, overwrite) {
options = Object.assign({}, options);
if (!options.hasOwnProperty('lean')) {
options.lean = true;
}
__setOptions.call(this, options, overwrite);
return this;
};
If you are worried about the accepted answer by #Leonid Beschastny, the places where you should not use lean, according to Mongoose docs are:
When you modify the query result
You use custom getters or transforms
Source: https://mongoosejs.com/docs/tutorials/lean.html#when-to-use-lean

Resources