1.0.0 • Published 8 years ago

mongoose-context v1.0.0

Weekly downloads
1
License
MIT
Repository
github
Last release
8 years ago

mongoose-context

Attach custom context data to mongoose that is inherited by models, document instances and sub-documents.

Installation

$ npm install mongoose-context

Introduction

When performing multiple asynchronous operations at the same time with Mongoose it sure would be nice if you could attach some custom data to a model and use it in callbacks to connect the right results back to the right operation. Using ExpressJS for instance, it would be very helpful if you could attach the request and response objects to a mongoose model and have these objects available in any callback invoked for operations using that particular model. That way, multiple incoming requests could easily be processed at the same time and the results linked back to the right response. Unfortunately, Mongoose uses singletons for models which makes this whole idea a bit tricky. Mongoose-context makes this process much easier by generating a mongoose instance with a custom object attached to it. Any models generated by this mongoose instance will inherit this same object and any document instances produced by any of these models will have access to this object as well. Even sub-documents will inherit this data from the main document instance. The plugin supports promises, queries, QueryStreams and middleware and is compatible with Mongoose versions 3.8 and 4.x.

The context inheritance model is best illustrated in a tree structure as follows:

mongoose --> context1
  model_A
    document_A1
      subdoc_A1[]
    document_A2
      subdoc_A2[]
  model_B
    document_B1
      subdoc_B1[]
    document_B2
      subdoc_B2[]

Object context1 is attached to a mongoose instance and available to all child models, documents and sub-documents generated with that parent instance through method $getContext() which is automatically added to all objects in the tree. Method $setContext(context) is also available to replace the attached and inherited context at any level. Unless $setContext() is used to replace the entire context, all child objects will point to the same inherited context which allows changes to the context data at any level to be visible to all objects sharing the context.

Usage

Mongoose-context exports the following functions:

.mongoose(context)

Creates a mongoose instance with specified context attached:

var mongoose = require('mongoose');
var contexter = require('mongoose-context');
var mySchema = mongoose.Schema({ field1: String, field2: Number });
var myContext = { prop1: 123, prop2: 'abc' };
var myData = { field1: 'someText', field2: 789 };

// Create mongoose instance with custom context
var instance = contexter.mongoose(myContext);

// Use method $getContext() to retrieve the context
var context = instance.$getContext();
console.log(context === myContext); // => true

// Create a model through this instance
var myModel = instance.model('myModel', mySchema);
console.log(myModel.$getContext() === myContext); // => true

// Retrieve a model through this instance
var modelA = instance.model('myModel');
console.log(modelA.$getContext() === myContext); // => true

// Created documents will inherit context from the model
modelA.create(myData, function(err, doc) {
  console.log(doc.$getContext() === myContext); // => true
	
  // Documents retrieved through the model will also inherit the context
  modelA.findOne({field1: 'someText'}, function(err, doc) {
    console.log(doc.$getContext() === myContext); // => true
  });
});

// The context is also available in middleware
mySchema.pre('save', function(next) {
  console.log(this.$getContext() === myContext); // => true
  next();
});
var modelB = instance.model('ModelB', mySchema); 
var doc = new modelB(data);
console.log(doc.$getContext() === myContext); // => true
doc.save();	// Will invoke the pre-save middleware function

Attaching a context using a custom mongoose connection:

var mongoose = require('mongoose');
var contexter = require('mongoose-context');
var mySchema = mongoose.Schema({ field1: String, field2: Number });
var myContext = { prop1: 123, prop2: 'abc' };

// Create mongoose instance with custom context
var instance = contexter.mongoose(myContext);

// Setup custom connection
var db = instance.createConnection('my_DB_URI');

// Create a model through this connection
var myModel = db.model('myModel', mySchema);
var context = myModel.$getContext();
console.log(context === myContext); // => true

// Retrieve a model through this connection
var modelA = db.model('myModel');
console.log(modelA.$getContext() === myContext); // => true

.model(context, db, name, schema, collection, skipInit)

It is not required to have a mongoose instance serve as the top level parent with the context attached. If it is more appropriate for each model to have its own context, the model method can be used to attach a context to a model directly, using the normal mongoose singleton:

var mongoose = require('mongoose');
var contexter = require('mongoose-context');
var mySchema = mongoose.Schema({ field1: String, field2: Number });
var myContext = { prop1: 123, prop2: 'abc' };

// Create a model with custom context attached
var myModel = contexter.model(myContext, 'myModel', mySchema);
var context = myModel.$getContext();
console.log(context === myContext);	// => true

// Retrieve a model with custom context attached
var modelA = contexter.model(myContext, 'myModel');
console.log(modelA.$getContext() === myContext); // => true

Attach context using a custom mongoose connection:

var mongoose = require('mongoose');
var contexter = require('mongoose-context');
var mySchema = mongoose.Schema({ field1: String, field2: Number });
var myContext = { prop1: 123, prop2: 'abc' };

// Setup custom connection
var db = mongoose.createConnection('my_DB_URI');

// Create a model through this connection
var myModel = contexter.model(myContext, db, 'myModel', mySchema); 
var context = myModel.$getContext();
console.log(context === myContext);	// => true

// Retrieve a model through this connection
var modelA = contexter.model(myContext, db, 'myModel');
console.log(modelA.$getContext() === myContext); // => true

.attach(context, target)

Use this method to attach a context to an existing mongoose model created through reqular means without any context attached and not part of the inheritance tree as depicted above. This method can also be used to attach a context to queries returned by model methods such as find() and findById(). All inheritance functionality as described above will apply to this parent object and its child objects.

var mongoose = require('mongoose');
var contexter = require('mongoose-context');
var mySchema = mongoose.Schema({ field1: String, field2: Number });

// Create a 'regular' mongoose model
var myModel = mongoose.model('myModel', mySchema);
console.log(myModel.$getContext === undefined); // => true

// Create a model instance with context1 attached 
var myContext1 = { prop1: 123, prop2: 'abc' };
var myModel1 = contexter.attach(myModel, myContext1);
console.log(myModel1.$getContext() === myContext1); // => true

// Create model instance with context2 attached 
var myContext2 = { prop1: 456, prop2: 'def' };
var myModel2 = contexter.attach(myModel, myContext2);
console.log(myModel2.$getContext() === myContext2); // => true

Events

Mongoose-context generates events at various stages of context management, allowing subscribers to take custom actions whenever these events occur. Please note that some events fire everytime a document is created or retrieved so extensive custom processing could have a significant negative impact on performance.

.on('contextualized', function(target) {})

Triggered whenever a model or query is converted to a contextualized instance with a context attached. The context is accessible through target.$getContext().

.on('instantiated', function(document) {})

Triggered whenever a context has been attached to a created or retrieved document. The context is accessible through document.$getContext();

.on('contextChanged', function(target) {})

Triggered whenever $setContext has been invoked on the target to replace the attached context. Target is either a model, query or document. The context is accessible through target.$getContext();

var mongoose = require('mongoose');
var contexter = require('mongoose-context');
var mySchema = mongoose.Schema({ field1: String, field2: Number });
var myContext = { prop1: 123, prop2: 'abc' };
var myData = { field1: 'someText', field2: 789 };

// Subscribe to context events
contexter.on('contextualized', function(target) {
  console.log('contextualized triggered'); 
});
contexter.on('instantiated', function(document) {
  console.log('instantiated triggered'); 
});
contexter.on('contextChanged', function(target) {
  console.log('contextChanged triggered');
});

// Creating a model will trigger the 'contextualized' event
var myModel = contexter.model(myContext, 'myModel', mySchema)

// Returned queries trigger the 'contextualized' event as well
var query = myModel.find(); 

// Creating a document instance with either new or the create method 
// will trigger the 'instantiated' event
var doc1 = new myModel(myData);
myModel.create(myData, function(err, doc) {});

// Queries will trigger an 'instantiated' event for each retrieved document
myModel.find(function(docs) {});   

// Changing the context will trigger the 'contextChanged' event
myModel.$setContext({ field1: 'something else'});