mongoose model inheritance within javascript prototypal object - node.js

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

Related

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

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')

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

Stubbing virtual attributes of Mongoose model

Is there a way to stub a virtual attribute of a Mongoose Model?
Assume Problem is a model class, and difficulty is a virtual attribute. delete Problem.prototype.difficulty returns false, and the attribute is still there, so I can't replace it with any value I want.
I also tried
var p = new Problem();
delete p.difficulty;
p.difficulty = Problem.INT_EASY;
It didn't work.
Assigning undefined to Problem.prototype.difficulty or using sinon.stub(Problem.prototype, 'difficulty').returns(Problem.INT_EASY);
would throw an exception "TypeError: Cannot read property 'scope' of undefined", while doing
var p = new Problem();
sinon.stub(p, 'difficulty').returns(Problem.INT_EASY);
would throw an error "TypeError: Attempted to wrap string property difficulty as function".
I am running out of ideas. Help me out! Thanks!
mongoose internally uses Object.defineProperty for all properties. Since they are defined as non-configurable, you can't delete them, and you can't re-configure them, either.
What you can do, though, is overwriting the model’s get and set methods, which are used to get and set any property:
var p = new Problem();
p.get = function (path, type) {
if (path === 'difficulty') {
return Problem.INT_EASY;
}
return Problem.prototype.get.apply(this, arguments);
};
Or, a complete example using sinon.js:
var mongoose = require('mongoose');
var sinon = require('sinon');
var problemSchema = new mongoose.Schema({});
problemSchema.virtual('difficulty').get(function () {
return Problem.INT_HARD;
});
var Problem = mongoose.model('Problem', problemSchema);
Problem.INT_EASY = 1;
Problem.INT_HARD = 2;
var p = new Problem();
console.log(p.difficulty);
sinon.stub(p, 'get').withArgs('difficulty').returns(Problem.INT_EASY);
console.log(p.difficulty);
As of the end of 2017 and the current Sinon version, stubbing only some of the arguments (e.g. only virtuals on mongoose models) can be achieved in the following manner
const ingr = new Model.ingredientModel({
productId: new ObjectID(),
});
// memorizing the original function + binding to the context
const getOrigin = ingr.get.bind(ingr);
const getStub = S.stub(ingr, 'get').callsFake(key => {
// stubbing ingr.$product virtual
if (key === '$product') {
return { nutrition: productNutrition };
}
// stubbing ingr.qty
else if (key === 'qty') {
return { numericAmount: 0.5 };
}
// otherwise return the original
else {
return getOrigin(key);
}
});
The solution is inspired by a bunch of different advises, including one by #Adrian Heine

How to access instance variables anywhere in the class for node.js

I am working on a node.js application with postgresql, using the express framework. I am trying to follow MVC as much as possible.
I want to generate query results in a model class and then pass them to a controller class. That controller class is actually defined in the routes, so that controller class can take the results and pass them as http response.
This is my database helper class, i.e. the model class. My problem is at the listener at the very end of the class.
exports.DatabaseHelper = function()
{
var allVenues;
var client;
var customEventEmitter;
this.init = function()
{
this.customEventEmitter = new events.EventEmitter();
client = new pg.Client(
{
host:'localhost',
port:5432,
database:'postgres',
user:'postgres',
password:'password'
});
}
this.getVenuesWithEvents = function(searchParams)
{
allVenues = new Array();
var query_for_venues;
this.init();
client.connect();
client.addListener("error",function()
{
sys.puts("postgresql interface error");
});
query_for_venues = client.query("select id, name, location, latitude, longitude, category_generalized from venues");
query_for_venues.addListener("row",function(row)
{
//some code
});
query_for_venues.addListener("end",function(result)
{
this.customEventEmitter.emit("data",allVenues);
//////////////////////////////////////////////////////////
//this line shows error....'this' refers to the query object so customEventEmitter is undefined
//customEventEmitter is my idea of sharing the query results to my controller class.
//but I cannot do this becasue of this error
console.log("after emission");
});
}
}
How can I access the customEventEmitter instance variable from within the listener?
Just remove this from your init function:
this.customEventEmitter = new events.EventEmitter();
So you'll have:
customEventEmitter = new events.EventEmitter();
And in your listener just emit the emitter without this as follows:
query_for_venues.addListener("end",function(result){
customEventEmitter.emit("data",allVenues);
console.log("after emission");
});
let me show you a nice trick.
you could change custom
var customEventEmitter;
to
this.customEventEmitter =null;
at the top of the function. then you can call
var self = this;
outside of the query function. then inside the query function you reference the outer "this" with self.
as in:
self.customEventEmitter.emit()
the methodology I just described is standard.

Instantiating objects with requireJS

I have a model object defined like this
blog.js
define({title: '', url: '', summary: ''});
I'm using the blog model to hold data from a service and I need to populate an array of blogs. How do I instantiate a new blog within a loop in another module?
I've tried passing in blog as a require object, but it maintains it's reference inside the loop leaving an array of the same object over and over again.
othermodule.js
define(['blog'], function(blog) {
//... more code
$.each(data, function(index, b) {
var tmpBlog = blog;
//...
list.push(tmpBlog);
});
});
I've also tried using var blog = require('blog'); yielding the same result.
So how do I instantiate the blog object using requirejs?
ok, I figured it out. I defined the blog model incorrectly. I needed to return a function blog(){...} instead like so:
define(function() {
return function blog() {
var self = this;
this.title = '';
//...
};
});
and the other module used it like this:
define(['models/blog'], function (model) {
var tmpBlog = new model();
tmpBlog.title = 'some title';
}

Resources