Merge branch 'fabien-feature/plugins'
This commit is contained in:
commit
caff62d8b3
|
@ -0,0 +1,67 @@
|
|||
var debug = require('debug')('loopback:mixin');
|
||||
var assert = require('assert');
|
||||
var DefaultModelBaseClass = require('./model.js');
|
||||
|
||||
function isModelClass(cls) {
|
||||
if (!cls) {
|
||||
return false;
|
||||
}
|
||||
return cls.prototype instanceof DefaultModelBaseClass;
|
||||
}
|
||||
|
||||
module.exports = MixinProvider;
|
||||
|
||||
function MixinProvider(modelBuilder) {
|
||||
this.modelBuilder = modelBuilder;
|
||||
this.mixins = {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply named mixin to the model class
|
||||
* @param {Model} modelClass
|
||||
* @param {String} name
|
||||
* @param {Object} options
|
||||
*/
|
||||
MixinProvider.prototype.applyMixin = function applyMixin(modelClass, name, options) {
|
||||
var fn = this.mixins[name];
|
||||
if (typeof fn === 'function') {
|
||||
if (modelClass.dataSource) {
|
||||
fn(modelClass, options || {});
|
||||
} else {
|
||||
modelClass.once('dataSourceAttached', function() {
|
||||
fn(modelClass, options || {});
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// Try model name
|
||||
var model = this.modelBuilder.getModel(name);
|
||||
if(model) {
|
||||
debug('Mixin is resolved to a model: %s', name);
|
||||
modelClass.mixin(model, options);
|
||||
} else {
|
||||
debug('Invalid mixin: %s', name);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Define a mixin with name
|
||||
* @param {String} name Name of the mixin
|
||||
* @param {*) mixin The mixin function or a model
|
||||
*/
|
||||
MixinProvider.prototype.define = function defineMixin(name, mixin) {
|
||||
assert(typeof mixin === 'function', 'The mixin must be a function or model class');
|
||||
if (this.mixins[name]) {
|
||||
debug('Duplicate mixin: %s', name);
|
||||
} else {
|
||||
debug('Defining mixin: %s', name);
|
||||
}
|
||||
if (isModelClass(mixin)) {
|
||||
this.mixins[name] = function (Model, options) {
|
||||
Model.mixin(mixin, options);
|
||||
};
|
||||
} else if (typeof mixin === 'function') {
|
||||
this.mixins[name] = mixin;
|
||||
}
|
||||
};
|
||||
|
|
@ -10,6 +10,7 @@ var DefaultModelBaseClass = require('./model.js');
|
|||
var List = require('./list.js');
|
||||
var ModelDefinition = require('./model-definition.js');
|
||||
var mergeSettings = require('./utils').mergeSettings;
|
||||
var MixinProvider = require('./mixins');
|
||||
|
||||
// Set up types
|
||||
require('./types')(ModelBuilder);
|
||||
|
@ -37,6 +38,7 @@ function ModelBuilder() {
|
|||
// create blank models pool
|
||||
this.models = {};
|
||||
this.definitions = {};
|
||||
this.mixins = new MixinProvider(this);
|
||||
this.defaultModelBaseClass = DefaultModelBaseClass;
|
||||
}
|
||||
|
||||
|
@ -188,7 +190,7 @@ ModelBuilder.prototype.define = function defineClass(className, properties, sett
|
|||
|
||||
// Add metadata to the ModelClass
|
||||
hiddenProperty(ModelClass, 'modelBuilder', modelBuilder);
|
||||
hiddenProperty(ModelClass, 'dataSource', modelBuilder); // Keep for back-compatibility
|
||||
hiddenProperty(ModelClass, 'dataSource', null); // Keep for back-compatibility
|
||||
hiddenProperty(ModelClass, 'pluralModelName', pluralName);
|
||||
hiddenProperty(ModelClass, 'relations', {});
|
||||
hiddenProperty(ModelClass, 'http', { path: '/' + pathName });
|
||||
|
@ -428,6 +430,20 @@ ModelBuilder.prototype.define = function defineClass(className, properties, sett
|
|||
ModelClass.registerProperty(propertyName);
|
||||
}
|
||||
|
||||
var mixinSettings = settings.mixins || {};
|
||||
keys = Object.keys(mixinSettings);
|
||||
size = keys.length;
|
||||
for (i = 0; i < size; i++) {
|
||||
var name = keys[i];
|
||||
var mixin = mixinSettings[name];
|
||||
if (mixin === true) {
|
||||
mixin = {};
|
||||
}
|
||||
if (typeof mixin === 'object') {
|
||||
modelBuilder.mixins.applyMixin(ModelClass, name, mixin);
|
||||
}
|
||||
}
|
||||
|
||||
ModelClass.emit('defined', ModelClass);
|
||||
|
||||
return ModelClass;
|
||||
|
|
21
lib/model.js
21
lib/model.js
|
@ -201,7 +201,11 @@ ModelBaseClass.prototype._initProperties = function (data, options) {
|
|||
* @param {Object} params Various property configuration
|
||||
*/
|
||||
ModelBaseClass.defineProperty = function (prop, params) {
|
||||
this.dataSource.defineProperty(this.modelName, prop, params);
|
||||
if(this.dataSource) {
|
||||
this.dataSource.defineProperty(this.modelName, prop, params);
|
||||
} else {
|
||||
this.modelBuilder.defineProperty(this.modelName, prop, params);
|
||||
}
|
||||
};
|
||||
|
||||
ModelBaseClass.getPropertyType = function (propName) {
|
||||
|
@ -387,7 +391,20 @@ ModelBaseClass.prototype.inspect = function () {
|
|||
};
|
||||
|
||||
ModelBaseClass.mixin = function (anotherClass, options) {
|
||||
return jutil.mixin(this, anotherClass, options);
|
||||
if (typeof anotherClass === 'string') {
|
||||
this.modelBuilder.mixins.applyMixin(this, anotherClass, options);
|
||||
} else {
|
||||
if (anotherClass.prototype instanceof ModelBaseClass) {
|
||||
var props = anotherClass.definition.properties;
|
||||
for (var i in props) {
|
||||
if (this.definition.properties[i]) {
|
||||
continue;
|
||||
}
|
||||
this.defineProperty(i, props[i]);
|
||||
}
|
||||
}
|
||||
return jutil.mixin(this, anotherClass, options);
|
||||
}
|
||||
};
|
||||
|
||||
ModelBaseClass.prototype.getDataSource = function () {
|
||||
|
|
|
@ -0,0 +1,91 @@
|
|||
// This test written in mocha+should.js
|
||||
var should = require('./init.js');
|
||||
|
||||
var jdb = require('../');
|
||||
var ModelBuilder = jdb.ModelBuilder;
|
||||
var DataSource = jdb.DataSource;
|
||||
var Memory = require('../lib/connectors/memory');
|
||||
|
||||
var modelBuilder = new ModelBuilder();
|
||||
var mixins = modelBuilder.mixins;
|
||||
|
||||
function timestamps(Model, options) {
|
||||
|
||||
Model.defineProperty('createdAt', { type: Date });
|
||||
Model.defineProperty('updatedAt', { type: Date });
|
||||
|
||||
var originalBeforeSave = Model.beforeSave;
|
||||
Model.beforeSave = function(next, data) {
|
||||
Model.applyTimestamps(data, this.isNewRecord());
|
||||
if (data.createdAt) {
|
||||
this.createdAt = data.createdAt;
|
||||
}
|
||||
if (data.updatedAt) {
|
||||
this.updatedAt = data.updatedAt;
|
||||
}
|
||||
if (originalBeforeSave) {
|
||||
originalBeforeSave.apply(this, arguments);
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
};
|
||||
|
||||
Model.applyTimestamps = function(data, creation) {
|
||||
data.updatedAt = new Date();
|
||||
if (creation) {
|
||||
data.createdAt = data.updatedAt;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
mixins.define('TimeStamp', timestamps);
|
||||
|
||||
describe('Model class', function () {
|
||||
|
||||
it('should define a mixin', function() {
|
||||
mixins.define('Example', function(Model, options) {
|
||||
Model.prototype.example = function() {
|
||||
return options;
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
it('should apply a mixin class', function() {
|
||||
var Address = modelBuilder.define('Address', {
|
||||
street: { type: 'string', required: true },
|
||||
city: { type: 'string', required: true }
|
||||
});
|
||||
|
||||
var memory = new DataSource('mem', {connector: Memory}, modelBuilder);
|
||||
var Item = memory.createModel('Item', { name: 'string' }, {
|
||||
mixins: { TimeStamp: true, demo: true, Address: true }
|
||||
});
|
||||
|
||||
var properties = Item.definition.properties;
|
||||
|
||||
properties.street.should.eql({ type: String, required: true });
|
||||
properties.city.should.eql({ type: String, required: true });
|
||||
});
|
||||
|
||||
it('should apply mixins', function(done) {
|
||||
var memory = new DataSource('mem', {connector: Memory}, modelBuilder);
|
||||
var Item = memory.createModel('Item', { name: 'string' }, {
|
||||
mixins: { TimeStamp: true, demo: { ok: true } }
|
||||
});
|
||||
|
||||
Item.mixin('Example', { foo: 'bar' });
|
||||
Item.mixin('other');
|
||||
|
||||
var properties = Item.definition.properties;
|
||||
properties.createdAt.should.eql({ type: Date });
|
||||
properties.updatedAt.should.eql({ type: Date });
|
||||
|
||||
Item.create({ name: 'Item 1' }, function(err, inst) {
|
||||
inst.createdAt.should.be.a.date;
|
||||
inst.updatedAt.should.be.a.date;
|
||||
inst.example().should.eql({ foo: 'bar' });
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
Loading…
Reference in New Issue