Last active
December 10, 2015 17:39
-
-
Save ducdigital/cb0926f1a27127369813 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* @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'); | |
}); | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'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