diff --git a/lib/adapters/memory.js b/lib/adapters/memory.js index ff68e337..f40d1753 100644 --- a/lib/adapters/memory.js +++ b/lib/adapters/memory.js @@ -191,10 +191,18 @@ function applyFilter(filter) { } return false; } + if(isNum(example.gt) && example.gt < value) return true; + if(isNum(example.gte) && example.gte <= value) return true; + if(isNum(example.lt) && example.lt > value) return true; + if(isNum(example.lte) && example.lte >= value) return true; } // not strict equality return (example !== null ? example.toString() : example) == (value !== null ? value.toString() : value); } + + function isNum(n) { + return typeof n === 'number'; + } } Memory.prototype.destroyAll = function destroyAll(model, callback) { @@ -209,15 +217,11 @@ Memory.prototype.count = function count(model, callback, where) { var cache = this.cache[model]; var data = Object.keys(cache) if (where) { - data = data.filter(function (id) { - var ok = true; - Object.keys(where).forEach(function (key) { - if (JSON.parse(cache[id])[key] != where[key]) { - ok = false; - } - }); - return ok; - }); + var filter = {where: where}; + data = data.map(function (id) { + return this.fromDb(model, cache[id]); + }.bind(this)); + data = data.filter(applyFilter(filter)); } process.nextTick(function () { callback(null, data.length); diff --git a/lib/dao.js b/lib/dao.js index 87a8e70d..1a64a6e5 100644 --- a/lib/dao.js +++ b/lib/dao.js @@ -157,6 +157,10 @@ DataAccessObject.create = function (data, callback) { return obj; }; +DataAccessObject.create.shared = true; +DataAccessObject.create.accepts = {arg: 'data', type: 'object'}; +DataAccessObject.create.returns = {arg: 'data', type: 'object'}; + function stillConnecting(schema, obj, args) { if (schema.connected) return false; // Connected @@ -358,6 +362,10 @@ DataAccessObject.findOne = function findOne(params, cb) { }); }; +DataAccessObject.findOne.shared = true; +DataAccessObject.findOne.accepts = {arg: 'filter', type: 'object'}; +DataAccessObject.findOne.returns = {arg: 'data', type: 'object'}; + /** * Destroy all records * @param {Function} cb - callback called with (err) @@ -529,6 +537,13 @@ DataAccessObject.prototype.updateAttribute = function updateAttribute(name, valu this.updateAttributes(data, callback); }; +DataAccessObject.prototype.updateAttribute.shared = true; +DataAccessObject.prototype.updateAttribute.accepts = [ + {arg: 'name', type: 'string', required: true}, + {arg: 'value', type: 'any', required: true} +]; +DataAccessObject.prototype.updateAttribute.returns = {arg: 'data', type: 'object'}; + /** * Update set of attributes * @@ -609,6 +624,9 @@ DataAccessObject.prototype.reload = function reload(callback) { this.constructor.find(this.id, callback); }; +DataAccessObject.prototype.reload.shared = true; +DataAccessObject.prototype.reload.returns = {arg: 'data', type: 'object'}; + /** * Define readonly property on object * diff --git a/lib/datasource.js b/lib/datasource.js index 7fdf2c51..e7ae5e51 100644 --- a/lib/datasource.js +++ b/lib/datasource.js @@ -1,6 +1,7 @@ /** * Module dependencies */ + var ModelBuilder = require('./model-builder.js').ModelBuilder; var jutil = require('./jutil'); var ModelBaseClass = require('./model.js'); @@ -57,9 +58,48 @@ function DataSource(name, settings) { ModelBuilder.call(this, arguments); this.setup(name, settings); - // default DataAccessObject - this.DataAccessObject = this.constructor.DataAccessObject; + // connector + var connector = this.connector(); + + // DataAccessObject - connector defined or supply the default + this.DataAccessObject = connector.DataAccessObject || this.constructor.DataAccessObject; this.DataAccessObject.call(this, arguments); + + // operation metadata + this._operations = {}; + + // define DataAccessObject methods + Object.keys(this.DataAccessObject).forEach(function (name) { + var fn = this.DataAccessObject[name]; + + if(typeof fn === 'function') { + this.defineOperation(name, { + accepts: fn.accepts, + returns: fn.returns, + http: fn.http, + remoteEnabled: fn.shared ? true : false, + scope: this.DataAccessObject, + fnName: name + }); + } + }.bind(this)); + + // define DataAccessObject.prototype methods + Object.keys(this.DataAccessObject.prototype).forEach(function (name) { + var fn = this.DataAccessObject.prototype[name]; + + if(typeof fn === 'function') { + this.defineOperation(name, { + prototype: true, + accepts: fn.accepts, + returns: fn.returns, + http: fn.http, + remoteEnabled: fn.shared ? true : false, + scope: this.DataAccessObject.prototype, + fnName: name + }); + } + }.bind(this)); }; util.inherits(DataSource, ModelBuilder); @@ -225,10 +265,40 @@ DataSource.prototype.define = function defineClass(className, properties, settin */ DataSource.prototype.mixin = function (ModelCtor) { + var ops = this.operations(); + var self = this; + var DAO = this.DataAccessObject; - // inherit DataAccessObject methods - jutil.mixin(ModelCtor, this.DataAccessObject); + // mixin DAO + jutil.mixin(ModelCtor, DAO); + + // decorate operations as alias functions + Object.keys(ops).forEach(function (name) { + var op = ops[name]; + var fn = op.scope[op.fnName]; + var scope; + if(op.enabled) { + scope = op.prototype ? ModelCtor.prototype : ModelCtor; + // var sfn = scope[name] = function () { + // op.scope[op.fnName].apply(self, arguments); + // } + Object.keys(op) + .filter(function (key) { + // filter out the following keys + return ~ [ + 'scope', + 'fnName', + 'prototype' + ].indexOf(key) + }) + .forEach(function (key) { + if(typeof op[key] !== 'undefined') { + op.scope[op.fnName][key] = op[key]; + } + }); + } + }); } /** @@ -253,6 +323,7 @@ DataSource.prototype.attach = function (ModelCtor) { // redefine the schema hiddenProperty(ModelCtor, 'schema', this); + ModelCtor.dataSource = this; // add to def this.definitions[className] = { @@ -1072,6 +1143,67 @@ DataSource.prototype.transaction = function() { return transaction; }; +/** + * Enable a data source operation remotely. + */ + +DataSource.prototype.enableRemote = function (operation) { + var op = this.getOperation(operation); + if(op) { + op.remoteEnabled = true; + } else { + throw new Error(operation + ' is not provided by the attached connector'); + } +} + +/** + * Disable a data source operation remotely. + */ + +DataSource.prototype.disableRemote = function (operation) { + var op = this.getOperation(operation); + if(op) { + op.remoteEnabled = false; + } else { + throw new Error(operation + ' is not provided by the attached connector'); + } +} + +/** + * Get an operation's metadata. + */ + +DataSource.prototype.getOperation = function (operation) { + var ops = this.operations(); + var opKeys = Object.keys(ops); + + for(var i = 0; i < opKeys.length; i++) { + var op = ops[opKeys[i]]; + + if(op.name === operation) { + return op; + } + } +} + +/** + * Get all operations. + */ + +DataSource.prototype.operations = function () { + return this._operations; +} + +/** + * Define an operation. + */ + +DataSource.prototype.defineOperation = function (name, options, fn) { + options.fn = fn; + options.name = name; + this._operations[name] = options; +} + /** * Define hidden property */ diff --git a/lib/model-builder.js b/lib/model-builder.js index 8f288b32..295b119d 100644 --- a/lib/model-builder.js +++ b/lib/model-builder.js @@ -167,6 +167,10 @@ ModelBuilder.prototype.define = function defineClass(className, properties, sett Object.keys(properties).forEach(cb); }; + ModelClass.attachTo = function (dataSource) { + dataSource.attach(this); + } + ModelClass.registerProperty = function (attr) { var DataType = properties[attr].type; if(!DataType) {