mongoose.model.find(callback) - How does it work? - node.js

I have been searching this, but could not understand. I have an app on node.js to access the MongoDB data using mongoose.
//contents of book.js
var Book = module.exports = mongoose.model('Books',genreSchema)
Book.getBooks = function(call,limit){
Book.find(call).limit(limit);
console.log(call.toString());
}
//contents of app.js
mongoose.connect('mongodb://localhost/bookstore');
var db = mongoose.connection;
Books = require('./models/book');
app.get('/api/book', function(req,res){
Books.getBooks(function(err, book){
if(err){ throw err;}
res.json(book);
});
});
The call.toString() returns the callback function definition in app.js. How does the Book.find(call) queries internally to match the collection name (though I don't explicitly specify the collection name) to fetch the records incorrectly?
I pass a definition to find() with absolutely no reference to what is to be fetched from DB except the db connection used.
I want to know how does this work?
Thanks!

When you define the Book model using:
var Book = module.exports = mongoose.model('Books', genreSchema)
Mongoose takes the lower-cased and pluralized model name of 'Books' to get the collection name of books.
You can explicitly define the collection name to use via the optional third parameter to model:
var Book = module.exports = mongoose.model('Books', genreSchema, 'books')

Related

generate hashed password on findOneAndUpdate [duplicate]

I'm trying to update counts on a pre hook. The issue is that for some unknown reason the findOneAndUpdate hook doesn't have access to the document, as far as I can tell.
I would like to do this:
source.pre('findOneAndUpdate', function (next) {
console.log('------------->>>>>> findOneAndUpdate: ');
this.objects = this.objects || [];
this.people = this.people || [];
this.events = this.events || [];
this.objectCount = this.objects.length;
this.peopleCount = this.people.length;
this.eventCount = this.events.length;
next();
});
But for some reason the this in the hook isn't the document, its a Query object which seems about useless.
What am I missing? How do I use a pre hook to update counts on a findOneAndUpdate?
You can do smthng like that ->
source.pre('findOneAndUpdate', function (next) {
console.log('------------->>>>>> findOneAndUpdate: ');
this._update.$set.objects = [];
this._update.$set.people = [];
this._update.$set.events = [];
next();
});
pay attention to _update.$set because in the context "this" will be a query. So you can easily add anything you want!
The documentation states:
Query middleware differs from document middleware in a subtle but important way: in document middleware, this refers to the document being updated. In query middleware, mongoose doesn't necessarily have a reference to the document being updated, so this refers to the query object rather than the document being updated.
An update action generally updates a document that only exists in the database (it tells the MongoDB server: "find document X and set property X to value Z"), so the full document isn't available to Mongoose and, hence, you can't update the counts (which requires access to at least the arrays whose length you want to determine).
As an aside: why do you need separate *Count properties in your schema anyway? If you want to query for arrays matching a certain size, you can use the $size operator on the arrays directly.
If you really do need the count properties, then for each update, you need to track the number of changes you made to each of the arrays (in terms of the number of items added/deleted) and use the $inc operator to adjust the counts.
I had a similar issue when I used the updateOne method and was also going to use the updateOne pre hook to make intermittent update before saving to the database. couldn't find a way for it to work. I ended up using the findOneAndUpdate pre hook and doing the updateOne in it.
schema.pre('findOneAndUpdate', async function(next){
const schema = this;
const { newUpdate } = schema.getUpdate();
const queryConditions = schema._condition
if(newUpdate){
//some mutation magic
await schema.updateOne(queryConditions, {newUpdate:"modified data"});
next()
}
next()
})
Another solution is to use the official MongoDB documentation on middleware. They explain why "this" does not refer to the document itself. You may try something in that sense:
source.pre('findOneAndUpdate', async function(next) {
const docToUpdate = await this.model.findOne(this.getFilter());
//modify the appropriate objects
docToUpdate.save(function(err) {
if(!err) {
console.log("Document Updated");
}
});
console.log(docToUpdate);
// The document that `findOneAndUpdate()` will modify
next();
});
This worked for me
SCHEMA.pre('findOneAndUpdate', function(next){
this._update.yourNestedElement
next();
});
schema.pre(['findOneAndUpdate'], async function(next) {
try {
const type = this.get('type')
const query = this.getQuery()
const doc = await this.findOne(query)
if (type) {
this.set('type', doc.type)
}
next()
} catch (e) {
next(new BaseError(e))
}
})
mongoose Documentation:
You cannot access the document being updated in pre('updateOne') or pre('findOneAndUpdate') query middleware. If you need to access the
document that will be updated, you need to execute an explicit query
for the document.
schema.pre('findOneAndUpdate', async function() {
const docToUpdate = await this.model.findOne(this.getQuery());
console.log(docToUpdate); // The document that `findOneAndUpdate()` will modify
});

How to cache a mongoose query in memory?

I have the following queries, which starts with the GetById method firing up, once that fires up and extracts data from another document, it saves into the race document.
I want to be able to cache the data after I save it for ten minutes. I have taken a look at cacheman library and not sure if it is the right tool for the job. what would be the best way to approach this ?
getById: function(opts,callback) {
var id = opts.action;
var raceData = { };
var self = this;
this.getService().findById(id,function(err,resp) {
if(err)
callback(null);
else {
raceData = resp;
self.getService().getPositions(id, function(err,positions) {
self.savePositions(positions,raceData,callback);
});
}
});
},
savePositions: function(positions,raceData,callback) {
var race = [];
_.each(positions,function(item) {
_.each(item.position,function(el) {
race.push(el);
});
});
raceData.positions = race;
this.getService().modelClass.update({'_id' : raceData._id },{ 'positions' : raceData.positions },callback(raceData));
}
I have recently coded and published a module called Monc. You could find the source code over here. You could find several useful methods to store, delete and retrieve data stored into the memory.
You may use it to cache Mongoose queries using simple nesting as
test.find({}).lean().cache().exec(function(err, docs) {
//docs are fetched into the cache.
});
Otherwise you may need to take a look at the core of Mongoose and override the prototype in order to provide a way to use cacheman as you original suggested.
Create a node module and force it to extend Mongoose as:
monc.hellocache(mongoose, {});
Inside your module you should extend the Mongoose.Query.prototype
exports.hellocache = module.exports.hellocache = function(mongoose, options, Aggregate) {
//require cacheman
var CachemanMemory = require('cacheman-memory');
var cache = new CachemanMemory();
var m = mongoose;
m.execAlter = function(caller, args) {
//do your stuff here
}
m.Query.prototype.exec = function(arg1, arg2) {
return m.execAlter.call(this, 'exec', arguments);
};
})
Take a look at Monc's source code as it may be a good reference on how you may extend and chain Mongoose methods
I will explain with npm redis package which stores key/value pairs in the cache server. keys are queries and redis stores only strings.
we have to make sure that keys are unique and consistent. So key value should store query and also name of the model that you are applying the query.
when you query, inside the mongoose library, there is
function Query(conditions, options, model, collection) {} //constructor function
responsible for query. inside this constructor,
Query.prototype.exec = function exec(op, callback) {}
this function is responsible executing the queries. so we have to manipulate this function and have it execute those tasks:
first check if we have any cached data related to the query
if yes respond to request right away and return
if no we need to respond to request and update our cache and then respond
const redis = require("client");
const redisUrl = "redis://127.0.0.1:6379";
const client = redis.createClient(redisUrl);
const util = require("util");
//client.get does not return promise
client.get = util.promisify(client.get);
const exec = mongoose.Query.prototype.exec;
//mongoose code is written using classical prototype inheritance for setting up objects and classes inside the library.
mongoose.Query.prototype.exec = async function() {
//crate a unique and consistent key
const key = JSON.stringify(
Object.assign({}, this.getQuery(), {
collection: this.mongooseCollection.name
})
);
//see if we have value for key in redis
const cachedValue = await redis.get(key);
//if we do return that as a mongoose model.
//the exec function expects us to return mongoose documents
if (cachedValue) {
const doc = JSON.parse(cacheValue);
return Array.isArray(doc)
? doc.map(d => new this.model(d))
: new this.model(doc);
}
const result = await exec.apply(this, arguments); //now exec function's original task.
client.set(key, JSON.stringify(result),"EX",6000);//it is saved to cache server make sure capital letters EX and time as seconds
};
if we store values as array of objects we need to make sure that each object is individullay converted to mongoose document.
this.model is a method inside the Query constructor and converts object to a mongoose document.
note that if you are storing nested values instead of client.get and client.set, use client.hset and client.hget
Now we monkey patched
Query.prototype.exec
so you do not need to export this function. wherever you have a query operation inside your code, mongoose will execute above code

mongoose model inheritance within javascript prototypal object

Suppose you have a route initialization like this required in your main:
module.exports = function(app) {
for (var name in names) {
var schema = new Schema({}) // schema that accepts anything
, m = mongoose.model(name, schema)
, controller = new TextController(m)
app.get('/path', controller.create.bind(controller))
// etc, etc
And TextController is defined externally as:
var TextController = function(Model) {
this.Model = Model
}
TextController.prototype.create = function(req, res) {
var aDoc = this.Model({ // this is the problematic bit
title: req.body.title
, content: req.body.content})
aDoc.save(function(err) {...})
}
For some reason, mongo saves this as an empty document even though the title and content params are the expected strings. As expected, this.Model is some sort of mongoose object, but it seems to be rejecting the save or the instantiation. Any ideas or suggestions?
Note: I added the controller.method.bind(controller) because it was the only way (I knew of) to get access to this.Model.
Edit: I've also tried the following:
var TextController = function(myCollection) {
this.myCollection = myCollection
this.list = function(req, res) {
this.myCollection.find({}, function { ... })
}
}
And also tried passing in the name and initializing the model within the scope of the function function(name) { this.myCollection = mongoose.model(name) ... }
This turns out to be unrelated to javascript prototypes and completely due to how mongoose does Mixed Type Schemas:
In order to tell mongoose the document has changed you need to markModified(field)
example here: http://mongoosejs.com/docs/schematypes.html#mixed

Native driver find from Mongoose model not returning Cursor

I'm trying to execute a native MongoDB find query via the collection property of a Mongoose Model. I'm not supplying a callback so I expect the find to return a Cursor object, but it returns undefined instead. According to the Mongoose docs, the driver being used is accessible via YourModel.collection and if I switch to purely using the native driver code find does return a Cursor so I can't figure out what's going on.
Here's a code snippet that reproduces the problem:
var db = mongoose.connect('localhost', 'test');
var userSchema = new Schema({
username: String,
emailAddress: String
});
var User = mongoose.model('user', userSchema);
var cursor = User.collection.find({});
// cursor will be set to undefined
I've tried to step into the code with node-inspector, but it's not letting me. Any idea what I'm doing wrong?
The native driver methods are all proxied to run on the nextTick so return values from the driver are not returned.
Instead, you can pass a callback and the 2nd arg returned is the cursor.
User.collection.find({}, function (err, cursor) {
//
});
Curious why you need to bypass mongoose?

Does mongoose allow for multiple database requests concurrently?

I read that Mongoose will only open one connection at maximum per collection, and there's no option to change this.
Does this mean that a slow mongo query will make all subsequent queries wait?
I know everything in node.js is non-blocking, but I'm wondering whether a slow query will delay the execution of all subsequent queries. And whether there is a way to change this.
It does only use one connection, if you use the default method where you do mongoose.connect(). To get around this, you can create multiple connections, and then tie a model pointing to the same schema to that connection.
Like so:
var conn = mongoose.createConnection('mongodb://localhost/test');
var conn2 = mongoose.createConnection('mongodb://localhost/test');
var model1 = conn.model('Model', Schema);
var model2 = conn2.model('Model', Schema);
model1.find({long query}, function() {
console.log("this will print out last");
});
model2.find({short query}, function() {
console.log("this will print out first");
});
Hope that helps.
Update
Hey, that does work. Updating from the comments, you can create a connection pool using createConnection. It lets you do multiple queries from the same model concurrently:
var conn = mongoose.createConnection('mongodb://localhost/test', {server:{poolSize:2}});
var model = conn.model('Model', Schema);
model.find({long query}, function() {
console.log("this will print out last");
});
model.find({short query}, function() {
console.log("this will print out first");
});
Update 2 -- Dec 2012
This answer may be slightly outdated now--I noticed I've been continuing to get upvotes, so I thought I would update it. The mongodb-native driver that mongoose wraps now has a default connection pool size of 5, so you probably don't need to explicitly specify it in mongoose.

Resources