Skip to content

Instantly share code, notes, and snippets.

@ducdigital
Last active December 10, 2015 17:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ducdigital/cb0926f1a27127369813 to your computer and use it in GitHub Desktop.
Save ducdigital/cb0926f1a27127369813 to your computer and use it in GitHub Desktop.
/*
* @file Mongoose inherit sub-schemas components test
* @copyright Copyright (c) LaBelleAssiette 2015. All rights reserved.
*/
'use strict';
var expect = require('chai').expect;
var should = require('chai').should();
var mongoose = require('mongoose');
var _ = require('lodash');
var InheritSchema = require('mongoose-inherit-subschema');
var Schema = mongoose.Schema;
describe('components.mongoose-inherit-subschema', function () {
var TestModel, Parent, Foo, Bar;
before(function () {
var ParentSchema = new InheritSchema({
__t: {
type: String
},
parentTextToInherit: {
type: String,
default: 'parentTextToInherit'
}
});
ParentSchema.virtual('virtual').get(function () {
return this.__t + '_virtual_ParentSchema';
});
ParentSchema.methods.method = function () {
return this.__t + '_method_ParentSchema';
};
ParentSchema.statics.statics = function (value) {
return 'ParentSchema_ParentSchema_statics: ' + value;
};
ParentSchema.virtual('ParentUniqueVirtual').get(function () {
return this.__t + '_virtual_ParentSchema_Parent_unique';
});
ParentSchema.methods.ParentUniqueMethod = function () {
return this.__t + '_method_ParentSchema_Parent_unique';
};
ParentSchema.statics.ParentUniqueStatics = function (value) {
return 'ParentSchema_ParentSchema_statics_Parent_unique: ' + value;
};
var FooSchema = ParentSchema.extend({
fooText: {
type: String
}
});
FooSchema.virtual('virtual').get(function () {
return this.__t + '_virtual_FooSchema';
});
FooSchema.methods.method = function () {
return this.__t + '_method_FooSchema';
};
FooSchema.statics.statics = function (value) {
return 'ParentSchema_FooSchema_statics: ' + value;
};
var BarSchema = ParentSchema.extend({
barText: {
type: String
}
});
BarSchema.virtual('virtual').get(function () {
return this.__t + '_virtual_BarSchema';
});
BarSchema.methods.method = function () {
return this.__t + '_method_BarSchema';
};
BarSchema.statics.statics = function (value) {
return 'ParentSchema_BarSchema_statics: ' + value;
};
var TestModelSchema = new Schema({
items: [ParentSchema]
});
Parent = mongoose.model('Parent', ParentSchema);
Foo = mongoose.modelInherit('Foo', FooSchema, Parent);
Bar = mongoose.modelInherit('Bar', BarSchema, Parent);
TestModel = mongoose.model('TestModel', TestModelSchema);
});
// Overidance
it('should run item own method base on the type of item added', function () {
var testObject = new TestModel();
testObject.items.push(new Foo());
testObject.items.push(new Bar());
testObject.items.push(new Parent());
_.each(testObject.items, function (item) {
var methodValue = item.method();
methodValue.should.contains(item.__t);
methodValue.should.contains('method');
methodValue.should.contains('ParentSchema');
});
});
it('should get item own virtuals base on the type of item added', function () {
var testObject = new TestModel();
testObject.items.push(new Foo());
testObject.items.push(new Bar());
testObject.items.push(new Parent());
_.each(testObject.items, function (item) {
var methodValue = item.virtual;
expect(methodValue).to.not.be.an('undefined');
methodValue.should.contains(item.__t);
methodValue.should.contains('virtual');
methodValue.should.contains(item.__t+'Schema');
});
});
it("should have different statics for different schema", function () {
_.each([Foo, Bar, Parent], function (itemType) {
var type = itemType.schema.tree.__t.default;
var staticInput = "Input";
var staticValue = itemType.statics(staticInput);
expect(staticValue).to.not.be.an('undefined');
staticValue.should.contains(type+'Schema');
staticValue.should.contains('statics');
staticValue.should.contains('ParentSchema');
staticValue.should.contains(staticInput);
});
});
// Inheritance
it('should make parent method inherited in children', function () {
var testObject = new TestModel();
testObject.items.push(new Foo());
testObject.items.push(new Bar());
testObject.items.push(new Parent());
_.each(testObject.items, function (item) {
var methodValue = item.ParentUniqueMethod();
expect(methodValue).to.not.be.an('undefined');
methodValue.should.contains(item.__t);
methodValue.should.contains('method').and.contains('Parent_unique');
methodValue.should.contains(item.__t);
});
});
it('should make the children inherit parent schemas', function () {
var items = [];
items[0] = new Foo();
items[1] = new Bar();
items[2] = new Parent();
_.each(items, function (item) {
var fieldValue = item.parentTextToInherit;
expect(fieldValue).to.not.be.an('undefined');
fieldValue.should.equal('parentTextToInherit');
});
});
it('should contains parent schema when pull from DB', function () {
var testObject = new TestModel();
testObject.items.push(new Foo());
testObject.items.push(new Bar());
testObject.items.push(new Parent());
_.each(testObject.items, function (item) {
var fieldValue = item.parentTextToInherit;
expect(fieldValue).to.not.be.an('undefined');
fieldValue.should.equal('parentTextToInherit');
});
});
it('should not able to set property that child schema does not own', function () {
var foo = new Foo();
var bar = new Bar();
foo.barText = "hiBartext";
bar.fooText = "hiFootext";
expect(foo).should.not.have.property('barText');
expect(bar).should.not.have.property('fooText');
});
it('should be able to set property that child schema own', function () {
var testObject = new TestModel();
var foo = new Foo();
var bar = new Bar();
foo.fooText = "FooText";
bar.barText = "BarText";
testObject.items.push(foo);
testObject.items.push(bar);
expect(foo).to.have.property('fooText').which.equal('FooText');
expect(bar).to.have.property('barText').which.equal('BarText');
expect(testObject.items[0]).to.have.property('fooText').which.equal('FooText');
expect(testObject.items[1]).to.have.property('barText').which.equal('BarText');
});
});
'use strict';
var mongoose = require('mongoose');
var _ = require('lodash');
var util = require('util');
var Schema = mongoose.Schema;
/**
* InheritSchema
* This is an extend of mongoose.Schema which add `.extend(schema, options)`
*/
function InheritSchema() {
if (!_.get(arguments, '1.childSchema', false)) {
this.originalSchemaDefinition = arguments[0];
this.isChildSchema = true;
}
Schema.apply(this, arguments);
this.extend = function (input, options) {
var i = _.cloneDeep(input);
if (this.options.discriminatorKey)
delete i[this.options.discriminatorKey];
this.add(i);
options = _.extend(options || {}, {childSchema: true});
input = this.cloneSchema(input, this.originalSchemaDefinition);
var newSchema = new InheritSchema(input, options);
for(var k in options) {
newSchema.options[k] = options[k];
}
return newSchema;
};
this.cloneSchema = function (schema, parent) {
for(var k in parent) {
if (schema[k] === undefined) {
schema[k] = parent[k];
}
}
return schema;
};
}
util.inherits(InheritSchema, Schema);
var cachedMongooseModel = mongoose.model;
mongoose.model = function () {
if(arguments[1] instanceof InheritSchema) {
var discriminatorKeyDef = {};
var discriminatorKey = arguments[1].options.discriminatorKey;
discriminatorKeyDef[discriminatorKey] = { type: String, default: arguments[0] };
arguments[1].add(discriminatorKeyDef);
}
return cachedMongooseModel.apply(this, arguments);
};
/**
* Mongoose.Connection.modelInherit
* This adds support for virtual getters and methods for sub-schemas.
* What works:
* - [x] Virtual's Getters
* - [ ] Virtual's Setters
* - [x] Methods
* - [x] Statics (Out of the box)
*/
mongoose.modelInherit = function(modelName, schema, baseModel) {
var discriminatorKey = baseModel.schema.options.discriminatorKey;
var model = this.model.apply(this, [modelName, schema]);
var self = this;
/* Cache a list of children schema names */
baseModel.children = baseModel.children || [];
baseModel.children.push(modelName);
baseModel.children = _.uniq(baseModel.children);
/*
Cache a list of children's virtuals
This will be use later on to look
*/
if (!baseModel.schema.virtualsMap) {
baseModel.schema.virtualsMap = {};
}
if (typeof baseModel.schema.virtualsMap[baseModel.modelName] === 'undefined') {
baseModel.schema.virtualsMap[baseModel.modelName] = _.cloneDeep(baseModel.schema.virtuals);
}
if (typeof baseModel.schema.virtualsMap[model.modelName] === 'undefined') {
baseModel.schema.virtualsMap[model.modelName] = _.cloneDeep(model.schema.virtuals);
}
/*
Create a proxy virtual that will call it subtype virtual base on discriminatorKeys
Does not support virtual setters. Use a method instead
*/
_.each(baseModel.schema.virtualsMap[model.modelName], function (v, vKey) {
if(['id', '_id'].indexOf(v.path) > -1) {
return;
}
if (!baseModel.schema.virtuals[vKey]) {
baseModel.schema.virtuals[vKey] = new mongoose.VirtualType(v.options, v.name);
}
baseModel.schema.virtuals[vKey].getters[0] = function () {
var discriminator = this[discriminatorKey];
if (!!baseModel.schema.virtualsMap[discriminator] && !!baseModel.schema.virtualsMap[discriminator][vKey]) {
return baseModel.schema.virtualsMap[discriminator][vKey].getters[0].apply(this, arguments);
}
};
});
/*
Create proxy function and call the method in children schemas base on discriminatorKeys
*/
var funcProxy = function (funcName) {
return function () {
var discriminator = this[discriminatorKey];
if (baseModel.children.indexOf(discriminator) > -1) {
return self.base.modelSchemas[discriminator].methods[funcName].apply(this, arguments);
}
return undefined;
};
};
_.each(schema.methods, function (method, key){
baseModel.schema.methods[key] = funcProxy(key);
});
/*
Give back the model
*/
return model;
};
module.exports = InheritSchema;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment