Merge pull request #197 from strongloop/feature/remoting-connector
Remote Connector
This commit is contained in:
commit
1c6f157b9f
91
Gruntfile.js
91
Gruntfile.js
|
@ -103,11 +103,7 @@ module.exports = function(grunt) {
|
||||||
// - PhantomJS
|
// - PhantomJS
|
||||||
// - IE (only Windows)
|
// - IE (only Windows)
|
||||||
browsers: [
|
browsers: [
|
||||||
'Chrome',
|
'Chrome'
|
||||||
'Firefox',
|
|
||||||
'Opera',
|
|
||||||
'Safari',
|
|
||||||
'PhantomJS'
|
|
||||||
],
|
],
|
||||||
|
|
||||||
// If browser does not capture in given timeout [ms], kill it
|
// If browser does not capture in given timeout [ms], kill it
|
||||||
|
@ -136,6 +132,83 @@ module.exports = function(grunt) {
|
||||||
// Add browserify to preprocessors
|
// Add browserify to preprocessors
|
||||||
preprocessors: {'test/*': ['browserify']}
|
preprocessors: {'test/*': ['browserify']}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
e2e: {
|
||||||
|
options: {
|
||||||
|
// base path, that will be used to resolve files and exclude
|
||||||
|
basePath: '',
|
||||||
|
|
||||||
|
// frameworks to use
|
||||||
|
frameworks: ['mocha', 'browserify'],
|
||||||
|
|
||||||
|
// list of files / patterns to load in the browser
|
||||||
|
files: [
|
||||||
|
'test/e2e/remote-connector.e2e.js'
|
||||||
|
],
|
||||||
|
|
||||||
|
// list of files to exclude
|
||||||
|
exclude: [
|
||||||
|
|
||||||
|
],
|
||||||
|
|
||||||
|
// test results reporter to use
|
||||||
|
// possible values: 'dots', 'progress', 'junit', 'growl', 'coverage'
|
||||||
|
reporters: ['dots'],
|
||||||
|
|
||||||
|
// web server port
|
||||||
|
port: 9876,
|
||||||
|
|
||||||
|
// cli runner port
|
||||||
|
runnerPort: 9100,
|
||||||
|
|
||||||
|
// enable / disable colors in the output (reporters and logs)
|
||||||
|
colors: true,
|
||||||
|
|
||||||
|
// level of logging
|
||||||
|
// possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
|
||||||
|
logLevel: 'warn',
|
||||||
|
|
||||||
|
// enable / disable watching file and executing tests whenever any file changes
|
||||||
|
autoWatch: true,
|
||||||
|
|
||||||
|
// Start these browsers, currently available:
|
||||||
|
// - Chrome
|
||||||
|
// - ChromeCanary
|
||||||
|
// - Firefox
|
||||||
|
// - Opera
|
||||||
|
// - Safari (only Mac)
|
||||||
|
// - PhantomJS
|
||||||
|
// - IE (only Windows)
|
||||||
|
browsers: [
|
||||||
|
'Chrome'
|
||||||
|
],
|
||||||
|
|
||||||
|
// If browser does not capture in given timeout [ms], kill it
|
||||||
|
captureTimeout: 60000,
|
||||||
|
|
||||||
|
// Continuous Integration mode
|
||||||
|
// if true, it capture browsers, run tests and exit
|
||||||
|
singleRun: false,
|
||||||
|
|
||||||
|
// Browserify config (all optional)
|
||||||
|
browserify: {
|
||||||
|
// extensions: ['.coffee'],
|
||||||
|
ignore: [
|
||||||
|
'nodemailer',
|
||||||
|
'passport',
|
||||||
|
'passport-local',
|
||||||
|
'superagent',
|
||||||
|
'supertest'
|
||||||
|
],
|
||||||
|
// transform: ['coffeeify'],
|
||||||
|
// debug: true,
|
||||||
|
// noParse: ['jquery'],
|
||||||
|
watch: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Add browserify to preprocessors
|
||||||
|
preprocessors: {'test/e2e/*': ['browserify']}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -148,6 +221,14 @@ module.exports = function(grunt) {
|
||||||
grunt.loadNpmTasks('grunt-contrib-watch');
|
grunt.loadNpmTasks('grunt-contrib-watch');
|
||||||
grunt.loadNpmTasks('grunt-karma');
|
grunt.loadNpmTasks('grunt-karma');
|
||||||
|
|
||||||
|
grunt.registerTask('e2e-server', function() {
|
||||||
|
var done = this.async();
|
||||||
|
var app = require('./test/fixtures/e2e/app');
|
||||||
|
app.listen(3000, done);
|
||||||
|
});
|
||||||
|
|
||||||
|
grunt.registerTask('e2e', ['e2e-server', 'karma:e2e']);
|
||||||
|
|
||||||
// Default task.
|
// Default task.
|
||||||
grunt.registerTask('default', ['browserify']);
|
grunt.registerTask('default', ['browserify']);
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
var loopback = require('../../');
|
||||||
|
var client = loopback();
|
||||||
|
var CartItem = require('./models').CartItem;
|
||||||
|
var remote = loopback.createDataSource({
|
||||||
|
connector: loopback.Remote,
|
||||||
|
root: 'http://localhost:3000'
|
||||||
|
});
|
||||||
|
|
||||||
|
client.model(CartItem);
|
||||||
|
CartItem.attachTo(remote);
|
||||||
|
|
||||||
|
// call the remote method
|
||||||
|
CartItem.sum(1, function(err, total) {
|
||||||
|
console.log('result:', err || total);
|
||||||
|
});
|
||||||
|
|
||||||
|
// call a built in remote method
|
||||||
|
CartItem.find(function(err, items) {
|
||||||
|
console.log(items);
|
||||||
|
});
|
|
@ -0,0 +1,35 @@
|
||||||
|
var loopback = require('../../');
|
||||||
|
|
||||||
|
var CartItem = exports.CartItem = loopback.DataModel.extend('CartItem', {
|
||||||
|
tax: {type: Number, default: 0.1},
|
||||||
|
price: Number,
|
||||||
|
item: String,
|
||||||
|
qty: {type: Number, default: 0},
|
||||||
|
cartId: Number
|
||||||
|
});
|
||||||
|
|
||||||
|
CartItem.sum = function(cartId, callback) {
|
||||||
|
this.find({where: {cartId: 1}}, function(err, items) {
|
||||||
|
var total = items
|
||||||
|
.map(function(item) {
|
||||||
|
return item.total();
|
||||||
|
})
|
||||||
|
.reduce(function(cur, prev) {
|
||||||
|
return prev + cur;
|
||||||
|
}, 0);
|
||||||
|
|
||||||
|
callback(null, total);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
loopback.remoteMethod(
|
||||||
|
CartItem.sum,
|
||||||
|
{
|
||||||
|
accepts: {arg: 'cartId', type: 'number'},
|
||||||
|
returns: {arg: 'total', type: 'number'}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
CartItem.prototype.total = function() {
|
||||||
|
return this.price * this.qty * 1 + this.tax;
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
var loopback = require('../../');
|
||||||
|
var server = module.exports = loopback();
|
||||||
|
var CartItem = require('./models').CartItem;
|
||||||
|
var memory = loopback.createDataSource({
|
||||||
|
connector: loopback.Memory
|
||||||
|
});
|
||||||
|
|
||||||
|
server.use(loopback.rest());
|
||||||
|
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.sum(1, function(err, total) {
|
||||||
|
console.log(total);
|
||||||
|
});
|
||||||
|
|
||||||
|
server.listen(3000);
|
1
index.js
1
index.js
|
@ -12,6 +12,7 @@ var datasourceJuggler = require('loopback-datasource-juggler');
|
||||||
loopback.Connector = require('./lib/connectors/base-connector');
|
loopback.Connector = require('./lib/connectors/base-connector');
|
||||||
loopback.Memory = require('./lib/connectors/memory');
|
loopback.Memory = require('./lib/connectors/memory');
|
||||||
loopback.Mail = require('./lib/connectors/mail');
|
loopback.Mail = require('./lib/connectors/mail');
|
||||||
|
loopback.Remote = require('./lib/connectors/remote');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Types
|
* Types
|
||||||
|
|
|
@ -56,7 +56,12 @@ app.remotes = function () {
|
||||||
if(this._remotes) {
|
if(this._remotes) {
|
||||||
return this._remotes;
|
return this._remotes;
|
||||||
} else {
|
} else {
|
||||||
var options = this.get('remoting') || {};
|
var options = {};
|
||||||
|
|
||||||
|
if(this.get) {
|
||||||
|
options = this.get('remoting');
|
||||||
|
}
|
||||||
|
|
||||||
return (this._remotes = RemoteObjects.create(options));
|
return (this._remotes = RemoteObjects.create(options));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,68 @@
|
||||||
|
/**
|
||||||
|
* Dependencies.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var assert = require('assert')
|
||||||
|
, compat = require('../compat')
|
||||||
|
, _ = require('underscore');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Export the RemoteConnector class.
|
||||||
|
*/
|
||||||
|
|
||||||
|
module.exports = RemoteConnector;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an instance of the connector with the given `settings`.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function RemoteConnector(settings) {
|
||||||
|
assert(typeof settings === 'object', 'cannot initiaze RemoteConnector without a settings object');
|
||||||
|
this.client = settings.client;
|
||||||
|
this.adapter = settings.adapter || 'rest';
|
||||||
|
this.protocol = settings.protocol || 'http'
|
||||||
|
this.root = settings.root || '';
|
||||||
|
this.host = settings.host || 'localhost';
|
||||||
|
this.port = settings.port || 3000;
|
||||||
|
|
||||||
|
if(settings.url) {
|
||||||
|
this.url = settings.url;
|
||||||
|
} else {
|
||||||
|
this.url = this.protocol + '://' + this.host + ':' + this.port + this.root;
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle mixins here
|
||||||
|
this.DataAccessObject = function() {};
|
||||||
|
}
|
||||||
|
|
||||||
|
RemoteConnector.prototype.connect = function() {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
RemoteConnector.initialize = function(dataSource, callback) {
|
||||||
|
var connector = dataSource.connector = new RemoteConnector(dataSource.settings);
|
||||||
|
connector.connect();
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
|
||||||
|
RemoteConnector.prototype.define = function(definition) {
|
||||||
|
var Model = definition.model;
|
||||||
|
var className = compat.getClassNameForRemoting(Model);
|
||||||
|
var url = this.url;
|
||||||
|
var adapter = this.adapter;
|
||||||
|
|
||||||
|
Model.remotes(function(err, remotes) {
|
||||||
|
var sharedClass = getSharedClass(remotes, className);
|
||||||
|
remotes.connect(url, adapter);
|
||||||
|
sharedClass
|
||||||
|
.methods()
|
||||||
|
.forEach(Model.createProxyMethod.bind(Model));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSharedClass(remotes, className) {
|
||||||
|
return _.find(remotes.classes(), function(sharedClass) {
|
||||||
|
return sharedClass.name === className;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function noop() {}
|
|
@ -311,6 +311,7 @@ loopback.autoAttachModel = function(ModelCtor) {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
loopback.Model = require('./models/model');
|
loopback.Model = require('./models/model');
|
||||||
|
loopback.DataModel = require('./models/data-model');
|
||||||
loopback.Email = require('./models/email');
|
loopback.Email = require('./models/email');
|
||||||
loopback.User = require('./models/user');
|
loopback.User = require('./models/user');
|
||||||
loopback.Application = require('./models/application');
|
loopback.Application = require('./models/application');
|
||||||
|
@ -330,6 +331,7 @@ var dataSourceTypes = {
|
||||||
};
|
};
|
||||||
|
|
||||||
loopback.Email.autoAttach = dataSourceTypes.MAIL;
|
loopback.Email.autoAttach = dataSourceTypes.MAIL;
|
||||||
|
loopback.DataModel.autoAttach = dataSourceTypes.DB;
|
||||||
loopback.User.autoAttach = dataSourceTypes.DB;
|
loopback.User.autoAttach = dataSourceTypes.DB;
|
||||||
loopback.AccessToken.autoAttach = dataSourceTypes.DB;
|
loopback.AccessToken.autoAttach = dataSourceTypes.DB;
|
||||||
loopback.Role.autoAttach = dataSourceTypes.DB;
|
loopback.Role.autoAttach = dataSourceTypes.DB;
|
||||||
|
|
|
@ -0,0 +1,331 @@
|
||||||
|
/*!
|
||||||
|
* 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;
|
||||||
|
// allow connectors to override the function by marking as delegate
|
||||||
|
fn._delegate = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Throw an error telling the user that the method is not available and why.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function throwNotAttached(modelName, methodName) {
|
||||||
|
throw new Error(
|
||||||
|
'Cannot call ' + modelName + '.'+ methodName + '().'
|
||||||
|
+ ' The ' + methodName + ' method has not been setup.'
|
||||||
|
+ ' The DataModel has not been correctly attached to a DataSource!'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* 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) {
|
||||||
|
throwNotAttached(this.modelName, 'create');
|
||||||
|
};
|
||||||
|
|
||||||
|
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) {
|
||||||
|
throwNotAttached(this.modelName, 'updateOrCreate');
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
throwNotAttached(this.modelName, 'findOrCreate');
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) {
|
||||||
|
throwNotAttached(this.modelName, 'exists');
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
throwNotAttached(this.modelName, 'find');
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
throwNotAttached(this.modelName, 'find');
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
throwNotAttached(this.modelName, 'findOne');
|
||||||
|
};
|
||||||
|
|
||||||
|
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) {
|
||||||
|
throwNotAttached(this.modelName, 'destroyAll');
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) {
|
||||||
|
throwNotAttached(this.modelName, 'deleteById');
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
throwNotAttached(this.modelName, 'count');
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
throwNotAttached(this.constructor.modelName, 'save');
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if the data model is new.
|
||||||
|
* @returns {Boolean}
|
||||||
|
*/
|
||||||
|
|
||||||
|
DataModel.prototype.isNewRecord = function () {
|
||||||
|
throwNotAttached(this.constructor.modelName, 'isNewRecord');
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete object from persistence
|
||||||
|
*
|
||||||
|
* @triggers `destroy` hook (async) before and after destroying object
|
||||||
|
*/
|
||||||
|
|
||||||
|
DataModel.prototype.remove =
|
||||||
|
DataModel.prototype.delete =
|
||||||
|
DataModel.prototype.destroy = function (cb) {
|
||||||
|
throwNotAttached(this.constructor.modelName, 'destroy');
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) {
|
||||||
|
throwNotAttached(this.constructor.modelName, 'updateAttribute');
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) {
|
||||||
|
throwNotAttached(this.modelName, 'updateAttributes');
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
throwNotAttached(this.constructor.modelName, 'reload');
|
||||||
|
};
|
|
@ -201,6 +201,72 @@ Model._getAccessTypeForMethod = function(method) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the `Application` the Model is attached to.
|
||||||
|
*
|
||||||
|
* @callback {Function} callback
|
||||||
|
* @param {Error} err
|
||||||
|
* @param {Application} app
|
||||||
|
* @end
|
||||||
|
*/
|
||||||
|
|
||||||
|
Model.getApp = function(callback) {
|
||||||
|
var Model = this;
|
||||||
|
if(this.app) {
|
||||||
|
callback(null, this.app);
|
||||||
|
} else {
|
||||||
|
Model.once('attached', function() {
|
||||||
|
assert(Model.app);
|
||||||
|
callback(null, Model.app);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the Model's `RemoteObjects`.
|
||||||
|
*
|
||||||
|
* @callback {Function} callback
|
||||||
|
* @param {Error} err
|
||||||
|
* @param {RemoteObjects} remoteObjects
|
||||||
|
* @end
|
||||||
|
*/
|
||||||
|
|
||||||
|
Model.remotes = function(callback) {
|
||||||
|
this.getApp(function(err, app) {
|
||||||
|
callback(null, app.remotes());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Create a proxy function for invoking remote methods.
|
||||||
|
*
|
||||||
|
* @param {SharedMethod} sharedMethod
|
||||||
|
*/
|
||||||
|
|
||||||
|
Model.createProxyMethod = function createProxyFunction(remoteMethod) {
|
||||||
|
var Model = this;
|
||||||
|
var scope = remoteMethod.isStatic ? Model : Model.prototype;
|
||||||
|
var original = scope[remoteMethod.name];
|
||||||
|
|
||||||
|
var fn = scope[remoteMethod.name] = function proxy() {
|
||||||
|
var args = Array.prototype.slice.call(arguments);
|
||||||
|
var lastArgIsFunc = typeof args[args.length - 1] === 'function';
|
||||||
|
var callback;
|
||||||
|
if(lastArgIsFunc) {
|
||||||
|
callback = args.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
Model.remotes(function(err, remotes) {
|
||||||
|
remotes.invoke(remoteMethod.stringName, args, callback);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for(var key in original) {
|
||||||
|
fn[key] = original[key];
|
||||||
|
}
|
||||||
|
fn._delegate = true;
|
||||||
|
}
|
||||||
|
|
||||||
// setup the initial model
|
// setup the initial model
|
||||||
Model.setup();
|
Model.setup();
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"debug": "~0.7.4",
|
"debug": "~0.7.4",
|
||||||
"express": "~3.4.8",
|
"express": "~3.4.8",
|
||||||
"strong-remoting": "~1.2.6",
|
"strong-remoting": "~1.3.1",
|
||||||
"inflection": "~1.3.5",
|
"inflection": "~1.3.5",
|
||||||
"passport": "~0.2.0",
|
"passport": "~0.2.0",
|
||||||
"passport-local": "~0.1.6",
|
"passport-local": "~0.1.6",
|
||||||
|
@ -38,7 +38,7 @@
|
||||||
"supertest": "~0.9.0",
|
"supertest": "~0.9.0",
|
||||||
"chai": "~1.9.0",
|
"chai": "~1.9.0",
|
||||||
"loopback-testing": "~0.1.2",
|
"loopback-testing": "~0.1.2",
|
||||||
"browserify": "~3.30.2",
|
"browserify": "~3.41.0",
|
||||||
"grunt": "~0.4.2",
|
"grunt": "~0.4.2",
|
||||||
"grunt-browserify": "~1.3.1",
|
"grunt-browserify": "~1.3.1",
|
||||||
"grunt-contrib-uglify": "~0.3.2",
|
"grunt-contrib-uglify": "~0.3.2",
|
||||||
|
@ -50,7 +50,7 @@
|
||||||
"karma-html2js-preprocessor": "~0.1.0",
|
"karma-html2js-preprocessor": "~0.1.0",
|
||||||
"karma-phantomjs-launcher": "~0.1.2",
|
"karma-phantomjs-launcher": "~0.1.2",
|
||||||
"karma": "~0.10.9",
|
"karma": "~0.10.9",
|
||||||
"karma-browserify": "0.1.0",
|
"karma-browserify": "~0.2.0",
|
||||||
"karma-mocha": "~0.1.1",
|
"karma-mocha": "~0.1.1",
|
||||||
"grunt-karma": "~0.6.2"
|
"grunt-karma": "~0.6.2"
|
||||||
},
|
},
|
||||||
|
@ -62,7 +62,8 @@
|
||||||
"express": "./lib/browser-express.js",
|
"express": "./lib/browser-express.js",
|
||||||
"connect": false,
|
"connect": false,
|
||||||
"passport": false,
|
"passport": false,
|
||||||
"passport-local": false
|
"passport-local": false,
|
||||||
|
"nodemailer": false
|
||||||
},
|
},
|
||||||
"license": {
|
"license": {
|
||||||
"name": "Dual MIT/StrongLoop",
|
"name": "Dual MIT/StrongLoop",
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
var path = require('path');
|
||||||
|
var loopback = require('../../');
|
||||||
|
var models = require('../fixtures/e2e/models');
|
||||||
|
var TestModel = models.TestModel;
|
||||||
|
var assert = require('assert');
|
||||||
|
|
||||||
|
describe('RemoteConnector', function() {
|
||||||
|
before(function() {
|
||||||
|
// setup the remote connector
|
||||||
|
var localApp = loopback();
|
||||||
|
var ds = loopback.createDataSource({
|
||||||
|
url: 'http://localhost:3000/api',
|
||||||
|
connector: loopback.Remote
|
||||||
|
});
|
||||||
|
localApp.model(TestModel);
|
||||||
|
TestModel.attachTo(ds);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be able to call create', function (done) {
|
||||||
|
TestModel.create({
|
||||||
|
foo: 'bar'
|
||||||
|
}, function(err, inst) {
|
||||||
|
if(err) return done(err);
|
||||||
|
assert(inst.id);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,14 @@
|
||||||
|
var loopback = require('../../../');
|
||||||
|
var path = require('path');
|
||||||
|
var app = module.exports = loopback();
|
||||||
|
var models = require('./models');
|
||||||
|
var TestModel = models.TestModel;
|
||||||
|
|
||||||
|
app.use(loopback.cookieParser({secret: app.get('cookieSecret')}));
|
||||||
|
var apiPath = '/api';
|
||||||
|
app.use(apiPath, loopback.rest());
|
||||||
|
app.use(loopback.static(path.join(__dirname, 'public')));
|
||||||
|
app.use(loopback.urlNotFound());
|
||||||
|
app.use(loopback.errorHandler());
|
||||||
|
app.model(TestModel);
|
||||||
|
TestModel.attachTo(loopback.memory());
|
|
@ -0,0 +1,4 @@
|
||||||
|
var loopback = require('../../../');
|
||||||
|
var DataModel = loopback.DataModel;
|
||||||
|
|
||||||
|
exports.TestModel = DataModel.extend('TestModel');
|
|
@ -0,0 +1,33 @@
|
||||||
|
var loopback = require('../');
|
||||||
|
|
||||||
|
describe('RemoteConnector', function() {
|
||||||
|
beforeEach(function(done) {
|
||||||
|
var LocalModel = this.LocalModel = loopback.DataModel.extend('LocalModel');
|
||||||
|
var RemoteModel = loopback.DataModel.extend('LocalModel');
|
||||||
|
var localApp = loopback();
|
||||||
|
var remoteApp = loopback();
|
||||||
|
localApp.model(LocalModel);
|
||||||
|
remoteApp.model(RemoteModel);
|
||||||
|
remoteApp.use(loopback.rest());
|
||||||
|
RemoteModel.attachTo(loopback.memory());
|
||||||
|
remoteApp.listen(0, function() {
|
||||||
|
var ds = loopback.createDataSource({
|
||||||
|
host: remoteApp.get('host'),
|
||||||
|
port: remoteApp.get('port'),
|
||||||
|
connector: loopback.Remote
|
||||||
|
});
|
||||||
|
|
||||||
|
LocalModel.attachTo(ds);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should alow methods to be called remotely', function (done) {
|
||||||
|
var data = {foo: 'bar'};
|
||||||
|
this.LocalModel.create(data, function(err, result) {
|
||||||
|
if(err) return done(err);
|
||||||
|
expect(result).to.deep.equal({id: 1, foo: 'bar'});
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in New Issue