Unified plugins into mixins
Mixin types: module function, module object, LDL json object.
This commit is contained in:
parent
455ee9ec63
commit
35776311fd
2
index.js
2
index.js
|
@ -1,9 +1,9 @@
|
|||
exports.mixins = require('./lib/mixins'); // require before ModelBuilder below - why?
|
||||
exports.ModelBuilder = exports.LDL = require('./lib/model-builder.js').ModelBuilder;
|
||||
exports.DataSource = exports.Schema = require('./lib/datasource.js').DataSource;
|
||||
exports.ModelBaseClass = require('./lib/model.js');
|
||||
exports.GeoPoint = require('./lib/geo.js').GeoPoint;
|
||||
exports.ValidationError = require('./lib/validations.js').ValidationError;
|
||||
exports.plugins = require('./lib/plugins');
|
||||
|
||||
exports.__defineGetter__('version', function () {
|
||||
return require('./package.json').version;
|
||||
|
|
|
@ -0,0 +1,87 @@
|
|||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
var extend = require('util')._extend;
|
||||
var inflection = require('inflection');
|
||||
var debug = require('debug')('loopback:mixin');
|
||||
var ModelBuilder = require('./model-builder').ModelBuilder;
|
||||
|
||||
var registry = exports.registry = {};
|
||||
var modelBuilder = new ModelBuilder();
|
||||
|
||||
exports.apply = function applyMixin(modelClass, name, options) {
|
||||
name = inflection.classify(name.replace(/-/g, '_'));
|
||||
var fn = registry[name];
|
||||
if (typeof fn === 'function') {
|
||||
if (modelClass.dataSource) {
|
||||
fn(modelClass, options || {});
|
||||
} else {
|
||||
modelClass.once('dataSourceAttached', function() {
|
||||
fn(modelClass, options || {});
|
||||
});
|
||||
}
|
||||
} else {
|
||||
debug('Invalid mixin: %s', name);
|
||||
}
|
||||
};
|
||||
|
||||
var defineMixin = exports.define = function defineMixin(name, mixin, ldl) {
|
||||
if (typeof mixin === 'function' || typeof mixin === 'object') {
|
||||
name = inflection.classify(name.replace(/-/g, '_'));
|
||||
if (registry[name]) {
|
||||
debug('Duplicate mixin: %s', name);
|
||||
} else {
|
||||
debug('Defining mixin: %s', name);
|
||||
}
|
||||
if (typeof mixin === 'object' && ldl) {
|
||||
var model = modelBuilder.define(name, mixin);
|
||||
registry[name] = function(Model, options) {
|
||||
Model.mixin(model, options);
|
||||
};
|
||||
} else if (typeof mixin === 'object') {
|
||||
registry[name] = function(Model, options) {
|
||||
extend(Model.prototype, mixin);
|
||||
};
|
||||
} else if (typeof mixin === 'function') {
|
||||
registry[name] = mixin;
|
||||
}
|
||||
} else {
|
||||
debug('Invalid mixin function: %s', name);
|
||||
}
|
||||
};
|
||||
|
||||
var loadMixins = exports.load = function loadMixins(dir) {
|
||||
var files = tryReadDir(path.resolve(dir));
|
||||
files.forEach(function(filename) {
|
||||
var filepath = path.resolve(path.join(dir, filename));
|
||||
var ext = path.extname(filename);
|
||||
var name = path.basename(filename, ext);
|
||||
var stats = fs.statSync(filepath);
|
||||
if (stats.isFile()) {
|
||||
if (ext in require.extensions) {
|
||||
var mixin = tryRequire(filepath);
|
||||
if (typeof mixin === 'function'
|
||||
|| typeof mixin === 'object') {
|
||||
defineMixin(name, mixin, ext === '.json');
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
loadMixins(path.join(__dirname, 'mixins'));
|
||||
|
||||
function tryReadDir() {
|
||||
try {
|
||||
return fs.readdirSync.apply(fs, arguments);
|
||||
} catch(e) {
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
function tryRequire(file) {
|
||||
try {
|
||||
return require(file);
|
||||
} catch(e) {
|
||||
}
|
||||
};
|
||||
|
|
@ -10,7 +10,7 @@ var DefaultModelBaseClass = require('./model.js');
|
|||
var List = require('./list.js');
|
||||
var ModelDefinition = require('./model-definition.js');
|
||||
var mergeSettings = require('./utils').mergeSettings;
|
||||
var plugins = require('./plugins');
|
||||
var mixins = require('./mixins');
|
||||
|
||||
// Set up types
|
||||
require('./types')(ModelBuilder);
|
||||
|
@ -429,19 +429,19 @@ ModelBuilder.prototype.define = function defineClass(className, properties, sett
|
|||
ModelClass.registerProperty(propertyName);
|
||||
}
|
||||
|
||||
var pluginSettings = settings.plugins || {};
|
||||
var keys = Object.keys(pluginSettings);
|
||||
var mixinSettings = settings.mixins || {};
|
||||
var keys = Object.keys(mixinSettings);
|
||||
var size = keys.length;
|
||||
for (i = 0; i < size; i++) {
|
||||
var name = keys[i];
|
||||
var plugin = pluginSettings[name];
|
||||
if (plugin === true) plugin = {};
|
||||
if (typeof plugin === 'object') {
|
||||
pluginSettings[name] = true;
|
||||
plugins.apply(name, ModelClass, plugin);
|
||||
var mixin = mixinSettings[name];
|
||||
if (mixin === true) mixin = {};
|
||||
if (typeof mixin === 'object') {
|
||||
mixinSettings[name] = true;
|
||||
mixins.apply(ModelClass, name, mixin);
|
||||
} else {
|
||||
// for settings metadata
|
||||
pluginSettings[name] = false;
|
||||
mixinSettings[name] = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
12
lib/model.js
12
lib/model.js
|
@ -12,7 +12,7 @@ var jutil = require('./jutil');
|
|||
var List = require('./list');
|
||||
var Hookable = require('./hooks');
|
||||
var validations = require('./validations.js');
|
||||
var plugins = require('./plugins');
|
||||
var mixins = require('./mixins');
|
||||
|
||||
// Set up an object for quick lookup
|
||||
var BASE_TYPES = {
|
||||
|
@ -388,7 +388,11 @@ ModelBaseClass.prototype.inspect = function () {
|
|||
};
|
||||
|
||||
ModelBaseClass.mixin = function (anotherClass, options) {
|
||||
return jutil.mixin(this, anotherClass, options);
|
||||
if (typeof anotherClass === 'string') {
|
||||
mixins.apply(this, anotherClass, options);
|
||||
} else {
|
||||
return jutil.mixin(this, anotherClass, options);
|
||||
}
|
||||
};
|
||||
|
||||
ModelBaseClass.prototype.getDataSource = function () {
|
||||
|
@ -403,9 +407,5 @@ ModelBaseClass.prototype.setStrict = function (strict) {
|
|||
this.__strict = strict;
|
||||
};
|
||||
|
||||
ModelBaseClass.plugin = function (name, options) {
|
||||
plugins.apply(name, this, options);
|
||||
};
|
||||
|
||||
jutil.mixin(ModelBaseClass, Hookable);
|
||||
jutil.mixin(ModelBaseClass, validations.Validatable);
|
||||
|
|
|
@ -1,69 +0,0 @@
|
|||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
var debug = require('debug')('loopback:plugin');
|
||||
|
||||
var registry = {};
|
||||
|
||||
exports.apply = function applyPlugin(name, modelClass, options) {
|
||||
var fn = registry[name];
|
||||
if (typeof fn === 'function') {
|
||||
if (modelClass.dataSource) {
|
||||
fn(modelClass, options || {});
|
||||
} else {
|
||||
modelClass.once('dataSourceAttached', function() {
|
||||
fn(modelClass, options || {});
|
||||
});
|
||||
}
|
||||
} else {
|
||||
debug('Invalid plugin: %s', name);
|
||||
}
|
||||
};
|
||||
|
||||
var definePlugin = exports.define = function definePlugin(name, fn) {
|
||||
if (typeof fn === 'function') {
|
||||
if (registry[name]) {
|
||||
debug('Duplicate plugin: %s', name);
|
||||
} else {
|
||||
debug('Defining plugin: %s', name);
|
||||
}
|
||||
registry[name] = fn;
|
||||
} else {
|
||||
debug('Invalid plugin function: %s', name);
|
||||
}
|
||||
};
|
||||
|
||||
var loadPlugins = exports.load = function loadPlugins(dir) {
|
||||
var files = tryReadDir(path.resolve(dir));
|
||||
files.forEach(function(filename) {
|
||||
var filepath = path.resolve(path.join(dir, filename));
|
||||
var ext = path.extname(filename);
|
||||
var name = path.basename(filename, ext);
|
||||
var stats = fs.statSync(filepath);
|
||||
if (stats.isFile()) {
|
||||
if (ext in require.extensions) {
|
||||
var plugin = tryRequire(filepath);
|
||||
if (typeof plugin === 'function') {
|
||||
definePlugin(name, plugin);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
loadPlugins(path.join(__dirname, 'plugins'));
|
||||
|
||||
function tryReadDir() {
|
||||
try {
|
||||
return fs.readdirSync.apply(fs, arguments);
|
||||
} catch(e) {
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
function tryRequire(file) {
|
||||
try {
|
||||
return require(file);
|
||||
} catch(e) {
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"properties": {
|
||||
"street": {
|
||||
"type": "string",
|
||||
"required": true
|
||||
},
|
||||
"city": {
|
||||
"type": "string",
|
||||
"required": true
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
module.exports = function timestamps(Model, options) {
|
||||
|
||||
Model.demoMixin = options.ok;
|
||||
|
||||
};
|
|
@ -0,0 +1,3 @@
|
|||
module.exports = {
|
||||
otherMixin: true
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
// This test written in mocha+should.js
|
||||
var should = require('./init.js');
|
||||
var assert = require('assert');
|
||||
var path = require('path');
|
||||
|
||||
var jdb = require('../');
|
||||
var ModelBuilder = jdb.ModelBuilder;
|
||||
var DataSource = jdb.DataSource;
|
||||
var Memory = require('../lib/connectors/memory');
|
||||
|
||||
var mixins = jdb.mixins;
|
||||
|
||||
describe('Model class', function () {
|
||||
|
||||
it('should define a mixin', function() {
|
||||
mixins.define('Example', function(Model, options) {
|
||||
Model.prototype.example = function() {
|
||||
return options;
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
it('should load mixins from directory', function() {
|
||||
var expected = [ 'TimeStamp', 'Example', 'Address', 'Demo', 'Other' ];
|
||||
mixins.load(path.join(__dirname, 'fixtures', 'mixins'));
|
||||
mixins.registry.should.have.property('TimeStamp');
|
||||
mixins.registry.should.have.property('Example');
|
||||
mixins.registry.should.have.property('Address');
|
||||
mixins.registry.should.have.property('Demo');
|
||||
mixins.registry.should.have.property('Other');
|
||||
});
|
||||
|
||||
it('should apply a mixin class', function() {
|
||||
var memory = new DataSource({connector: Memory});
|
||||
var Item = memory.createModel('Item', { name: 'string' }, {
|
||||
mixins: { TimeStamp: true, demo: true, Address: true }
|
||||
});
|
||||
|
||||
var modelBuilder = new ModelBuilder();
|
||||
var Address = modelBuilder.define('Address', {
|
||||
street: { type: 'string', required: true },
|
||||
city: { type: 'string', required: true }
|
||||
});
|
||||
|
||||
Item.mixin(Address);
|
||||
|
||||
var def = memory.getModelDefinition('Item');
|
||||
var properties = def.toJSON().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({connector: Memory});
|
||||
var Item = memory.createModel('Item', { name: 'string' }, {
|
||||
mixins: { TimeStamp: true, demo: { ok: true }, Address: true }
|
||||
});
|
||||
|
||||
Item.mixin('Example', { foo: 'bar' });
|
||||
Item.mixin('other');
|
||||
|
||||
var def = memory.getModelDefinition('Item');
|
||||
var properties = def.toJSON().properties;
|
||||
properties.createdAt.should.eql({ type: 'Date' });
|
||||
properties.updatedAt.should.eql({ type: 'Date' });
|
||||
|
||||
// properties.street.should.eql({ type: 'String', required: true });
|
||||
// properties.city.should.eql({ type: 'String', required: true });
|
||||
|
||||
Item.demoMixin.should.be.true;
|
||||
Item.prototype.otherMixin.should.be.true;
|
||||
|
||||
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();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
|
@ -1,44 +0,0 @@
|
|||
// This test written in mocha+should.js
|
||||
var should = require('./init.js');
|
||||
var assert = require('assert');
|
||||
|
||||
var jdb = require('../');
|
||||
var ModelBuilder = jdb.ModelBuilder;
|
||||
var DataSource = jdb.DataSource;
|
||||
var Memory = require('../lib/connectors/memory');
|
||||
|
||||
var plugins = jdb.plugins;
|
||||
|
||||
describe('Model class', function () {
|
||||
|
||||
it('should define a plugin', function() {
|
||||
plugins.define('example', function(Model, options) {
|
||||
Model.prototype.example = function() {
|
||||
return options;
|
||||
};
|
||||
});
|
||||
})
|
||||
|
||||
it('should apply plugin', function(done) {
|
||||
var memory = new DataSource({connector: Memory});
|
||||
var Item = memory.createModel('Item', { name: 'string' }, {
|
||||
plugins: { timestamps: true }
|
||||
});
|
||||
|
||||
Item.plugin('example', { foo: 'bar' });
|
||||
|
||||
var def = memory.getModelDefinition('Item');
|
||||
var json = def.toJSON();
|
||||
var properties = json.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