From dcdbef861e73b52a2718eb189afb3bbd2c740cc0 Mon Sep 17 00:00:00 2001 From: Ritchie Martori Date: Wed, 19 Feb 2014 17:09:36 -0800 Subject: [PATCH] Add Remote connector --- example/client-server/models.js | 2 +- example/client-server/server.js | 16 +- lib/connectors/remote.js | 4 +- lib/loopback.js | 2 + lib/models/data-model.js | 319 ++++++++++++++++++++++++++++++++ 5 files changed, 331 insertions(+), 12 deletions(-) create mode 100644 lib/models/data-model.js diff --git a/example/client-server/models.js b/example/client-server/models.js index a18ab2e2..c14485c8 100644 --- a/example/client-server/models.js +++ b/example/client-server/models.js @@ -1,6 +1,6 @@ var loopback = require('../../'); -var CartItem = exports.CartItem = loopback.Model.extend('CartItem', { +var CartItem = exports.CartItem = loopback.DataModel.extend('CartItem', { tax: {type: Number, default: 0.1}, price: Number, item: String, diff --git a/example/client-server/server.js b/example/client-server/server.js index 4ab292e7..7e466a56 100644 --- a/example/client-server/server.js +++ b/example/client-server/server.js @@ -11,14 +11,14 @@ server.model(CartItem); CartItem.attachTo(memory); // test data -// CartItem.create([ -// {item: 'red hat', qty: 6, price: 19.99, cartId: 1}, -// {item: 'green shirt', qty: 1, price: 14.99, cartId: 1}, -// {item: 'orange pants', qty: 58, price: 9.99, cartId: 1} -// ]); +CartItem.create([ + {item: 'red hat', qty: 6, price: 19.99, cartId: 1}, + {item: 'green shirt', qty: 1, price: 14.99, cartId: 1}, + {item: 'orange pants', qty: 58, price: 9.99, cartId: 1} +]); -// CartItem.sum(1, function(err, total) { -// console.log(total); -// }) +CartItem.sum(1, function(err, total) { + console.log(total); +}); server.listen(3000); diff --git a/lib/connectors/remote.js b/lib/connectors/remote.js index 6f2f7e15..da825912 100644 --- a/lib/connectors/remote.js +++ b/lib/connectors/remote.js @@ -20,10 +20,8 @@ function RemoteConnector(settings) { assert(typeof settings === 'object', 'cannot initiaze RemoteConnector without a settings object'); this.client = settings.client; this.root = settings.root; - this.client = settings.client; - this.remotes = this.client.remotes(); + this.remotes = settings.remotes; this.adapter = settings.adapter || 'rest'; - assert(this.client, 'RemoteConnector: settings.client is required'); assert(this.root, 'RemoteConnector: settings.root is required'); // handle mixins here diff --git a/lib/loopback.js b/lib/loopback.js index 6a057a44..bd1f2989 100644 --- a/lib/loopback.js +++ b/lib/loopback.js @@ -310,6 +310,7 @@ loopback.autoAttachModel = function(ModelCtor) { */ loopback.Model = require('./models/model'); +loopback.DataModel = require('./models/data-model'); loopback.Email = require('./models/email'); loopback.User = require('./models/user'); loopback.Application = require('./models/application'); @@ -329,6 +330,7 @@ var dataSourceTypes = { }; loopback.Email.autoAttach = dataSourceTypes.MAIL; +loopback.DataModel.autoAttach = dataSourceTypes.DB; loopback.User.autoAttach = dataSourceTypes.DB; loopback.AccessToken.autoAttach = dataSourceTypes.DB; loopback.Role.autoAttach = dataSourceTypes.DB; diff --git a/lib/models/data-model.js b/lib/models/data-model.js new file mode 100644 index 00000000..ffdd3195 --- /dev/null +++ b/lib/models/data-model.js @@ -0,0 +1,319 @@ +/*! + * Module Dependencies. + */ +var Model = require('./model'); + +/** + * Extends Model with basic query and CRUD support. + * + * @class DataModel + * @param {Object} data + */ + +var DataModel = module.exports = Model.extend('DataModel'); + +/*! + * Configure the remoting attributes for a given function + * @param {Function} fn The function + * @param {Object} options The options + * @private + */ + +function setRemoting(fn, options) { + options = options || {}; + for (var opt in options) { + if (options.hasOwnProperty(opt)) { + fn[opt] = options[opt]; + } + } + fn.shared = true; +} + +/*! + * Convert null callbacks to 404 error objects. + * @param {HttpContext} ctx + * @param {Function} cb + */ + +function convertNullToNotFoundError(ctx, cb) { + if (ctx.result !== null) return cb(); + + var modelName = ctx.method.sharedClass.name; + var id = ctx.getArgByName('id'); + var msg = 'Unkown "' + modelName + '" id "' + id + '".'; + var error = new Error(msg); + error.statusCode = error.status = 404; + cb(error); +} + +/** + * Create new instance of Model class, saved in database + * + * @param data [optional] + * @param callback(err, obj) + * callback called with arguments: + * + * - err (null or Error) + * - instance (null or Model) + */ + +DataModel.create = function (data, callback) { + +}; + +setRemoting(DataModel.create, { + description: 'Create a new instance of the model and persist it into the data source', + accepts: {arg: 'data', type: 'object', description: 'Model instance data', http: {source: 'body'}}, + returns: {arg: 'data', type: 'object', root: true}, + http: {verb: 'post', path: '/'} +}); + +/** + * Update or insert a model instance + * @param {Object} data The model instance data + * @param {Function} [callback] The callback function + */ + +DataModel.upsert = DataModel.updateOrCreate = function upsert(data, callback) { + +}; + +// upsert ~ remoting attributes +setRemoting(DataModel.upsert, { + description: 'Update an existing model instance or insert a new one into the data source', + accepts: {arg: 'data', type: 'object', description: 'Model instance data', http: {source: 'body'}}, + returns: {arg: 'data', type: 'object', root: true}, + http: {verb: 'put', path: '/'} +}); + +/** + * Find one record, same as `all`, limited by 1 and return object, not collection, + * if not found, create using data provided as second argument + * + * @param {Object} query - search conditions: {where: {test: 'me'}}. + * @param {Object} data - object to create. + * @param {Function} cb - callback called with (err, instance) + */ + +DataModel.findOrCreate = function findOrCreate(query, data, callback) { + +}; + +/** + * Check whether a model instance exists in database + * + * @param {id} id - identifier of object (primary key value) + * @param {Function} cb - callbacl called with (err, exists: Bool) + */ + +DataModel.exists = function exists(id, cb) { + +}; + +// exists ~ remoting attributes +setRemoting(DataModel.exists, { + description: 'Check whether a model instance exists in the data source', + accepts: {arg: 'id', type: 'any', description: 'Model id', required: true}, + returns: {arg: 'exists', type: 'any'}, + http: {verb: 'get', path: '/:id/exists'} +}); + +/** + * Find object by id + * + * @param {*} id - primary key value + * @param {Function} cb - callback called with (err, instance) + */ + +DataModel.findById = function find(id, cb) { + +}; + +// find ~ remoting attributes +setRemoting(DataModel.findById, { + description: 'Find a model instance by id from the data source', + accepts: {arg: 'id', type: 'any', description: 'Model id', required: true}, + returns: {arg: 'data', type: 'any', root: true}, + http: {verb: 'get', path: '/:id'}, + rest: {after: convertNullToNotFoundError} +}); + +/** + * Find all instances of Model, matched by query + * make sure you have marked as `index: true` fields for filter or sort + * + * @param {Object} params (optional) + * + * - where: Object `{ key: val, key2: {gt: 'val2'}}` + * - include: String, Object or Array. See DataModel.include documentation. + * - order: String + * - limit: Number + * - skip: Number + * + * @param {Function} callback (required) called with arguments: + * + * - err (null or Error) + * - Array of instances + */ + +DataModel.find = function find(params, cb) { + +}; + +// all ~ remoting attributes +setRemoting(DataModel.find, { + description: 'Find all instances of the model matched by filter from the data source', + accepts: {arg: 'filter', type: 'object', description: 'Filter defining fields, where, orderBy, offset, and limit'}, + returns: {arg: 'data', type: 'array', root: true}, + http: {verb: 'get', path: '/'} +}); + +/** + * Find one record, same as `all`, limited by 1 and return object, not collection + * + * @param {Object} params - search conditions: {where: {test: 'me'}} + * @param {Function} cb - callback called with (err, instance) + */ + +DataModel.findOne = function findOne(params, cb) { + +}; + +setRemoting(DataModel.findOne, { + description: 'Find first instance of the model matched by filter from the data source', + accepts: {arg: 'filter', type: 'object', description: 'Filter defining fields, where, orderBy, offset, and limit'}, + returns: {arg: 'data', type: 'object', root: true}, + http: {verb: 'get', path: '/findOne'} +}); + +/** + * Destroy all matching records + * @param {Object} [where] An object that defines the criteria + * @param {Function} [cb] - callback called with (err) + */ + +DataModel.remove = +DataModel.deleteAll = +DataModel.destroyAll = function destroyAll(where, cb) { + +}; + +/** + * Destroy a record by id + * @param {*} id The id value + * @param {Function} cb - callback called with (err) + */ + +DataModel.removeById = +DataModel.deleteById = +DataModel.destroyById = function deleteById(id, cb) { + +}; + +// deleteById ~ remoting attributes +setRemoting(DataModel.deleteById, { + description: 'Delete a model instance by id from the data source', + accepts: {arg: 'id', type: 'any', description: 'Model id', required: true}, + http: {verb: 'del', path: '/:id'} +}); + +/** + * Return count of matched records + * + * @param {Object} where - search conditions (optional) + * @param {Function} cb - callback, called with (err, count) + */ + +DataModel.count = function (where, cb) { + +}; + +// count ~ remoting attributes +setRemoting(DataModel.count, { + description: 'Count instances of the model matched by where from the data source', + accepts: {arg: 'where', type: 'object', description: 'Criteria to match model instances'}, + returns: {arg: 'count', type: 'number'}, + http: {verb: 'get', path: '/count'} +}); + +/** + * Save instance. When instance haven't id, create method called instead. + * Triggers: validate, save, update | create + * @param options {validate: true, throws: false} [optional] + * @param callback(err, obj) + */ + +DataModel.prototype.save = function (options, callback) { + +}; + + +/** + * Determine if the data model is new. + * @returns {Boolean} + */ + +DataModel.prototype.isNewRecord = function () { + +}; + +/** + * Delete object from persistence + * + * @triggers `destroy` hook (async) before and after destroying object + */ + +DataModel.prototype.remove = +DataModel.prototype.delete = +DataModel.prototype.destroy = function (cb) { + +}; + +/** + * Update single attribute + * + * equals to `updateAttributes({name: value}, cb) + * + * @param {String} name - name of property + * @param {Mixed} value - value of property + * @param {Function} callback - callback called with (err, instance) + */ + +DataModel.prototype.updateAttribute = function updateAttribute(name, value, callback) { + +}; + +/** + * Update set of attributes + * + * this method performs validation before updating + * + * @trigger `validation`, `save` and `update` hooks + * @param {Object} data - data to update + * @param {Function} callback - callback called with (err, instance) + */ + +DataModel.prototype.updateAttributes = function updateAttributes(data, cb) { + +}; + +// updateAttributes ~ remoting attributes +setRemoting(DataModel.prototype.updateAttributes, { + description: 'Update attributes for a model instance and persist it into the data source', + accepts: {arg: 'data', type: 'object', http: {source: 'body'}, description: 'An object of model property name/value pairs'}, + returns: {arg: 'data', type: 'object', root: true}, + http: {verb: 'put', path: '/'} +}); + +/** + * Reload object from persistence + * + * @requires `id` member of `object` to be able to call `find` + * @param {Function} callback - called with (err, instance) arguments + */ + +DataModel.prototype.reload = function reload(callback) { + if (stillConnecting(this.getDataSource(), this, arguments)) return; + + this.constructor.findById(getIdValue(this.constructor, this), callback); +};