diff --git a/README.md b/README.md index 191a1eec..280811ca 100644 --- a/README.md +++ b/README.md @@ -369,6 +369,21 @@ Other wildcard examples // run before any instance method eg. User.prototype.save User.beforeRemote('prototype.*', ...); + // prevent password hashes from being sent to clients + User.afterRemote('**', function (ctx, user, next) { + if(ctx.result) { + if(Array.isArray(ctx.result)) { + ctx.result.forEach(function (result) { + result.password = undefined; + }); + } else { + ctx.result.password = undefined; + } + } + + next(); + }); + #### Context Remote hooks are provided with a Context `ctx` object which contains transport specific data (eg. for http: `req` and `res`). The `ctx` object also has a set of consistent apis across transports. @@ -377,6 +392,10 @@ Remote hooks are provided with a Context `ctx` object which contains transport s A `Model` representing the user calling the method remotely. **Note:** this is undefined if the remote method is not invoked by a logged in user. +##### ctx.result + +During `afterRemote` hooks, `ctx.result` will contain the data about to be sent to a client. Modify this object to transform data before it is sent. + ##### Rest When [asteroid.rest](#asteroidrest) is used the following `ctx` properties are available. diff --git a/lib/application.js b/lib/application.js index b36ca716..00e90c9f 100644 --- a/lib/application.js +++ b/lib/application.js @@ -5,8 +5,7 @@ var DataSource = require('jugglingdb').DataSource , ModelBuilder = require('jugglingdb').ModelBuilder , assert = require('assert') - , RemoteObjects = require('sl-remoting') - , i8n = require('inflection'); + , RemoteObjects = require('sl-remoting'); /** * Export the app prototype. @@ -55,6 +54,9 @@ app._models = []; app.model = function (Model) { this._models.push(Model); Model.app = this; + if(Model._remoteHooks) { + Model._remoteHooks.emit('attached', app); + } } /** @@ -65,7 +67,6 @@ app.models = function () { return this._models; } - /** * Get all remote objects. */ diff --git a/lib/asteroid.js b/lib/asteroid.js index acf011cf..e0d19243 100644 --- a/lib/asteroid.js +++ b/lib/asteroid.js @@ -4,11 +4,14 @@ var express = require('express') , fs = require('fs') + , EventEmitter = require('events').EventEmitter , path = require('path') , proto = require('./application') , utils = require('express/node_modules/connect').utils , DataSource = require('jugglingdb').DataSource - , ModelBuilder = require('jugglingdb').ModelBuilder; + , ModelBuilder = require('jugglingdb').ModelBuilder + , assert = require('assert') + , i8n = require('inflection'); /** * Expose `createApplication()`. @@ -83,9 +86,52 @@ asteroid.errorHandler.title = 'Asteroid'; asteroid.createDataSource = function (name, options) { var ds = new DataSource(name, options); ds.createModel = function (name, properties, settings) { - var Model = asteroid.createModel(name, properties, settings); - Model.attachTo(ds); - return Model; + var ModelCtor = asteroid.createModel(name, properties, settings); + ModelCtor.attachTo(ds); + + var hasMany = ModelCtor.hasMany; + + if(hasMany) { + ModelCtor.hasMany = function (anotherClass, params) { + var origArgs = arguments; + var thisClass = this, thisClassName = this.modelName; + params = params || {}; + if (typeof anotherClass === 'string') { + params.as = anotherClass; + if (params.model) { + anotherClass = params.model; + } else { + var anotherClassName = i8n.singularize(anotherClass).toLowerCase(); + for(var name in this.schema.models) { + if (name.toLowerCase() === anotherClassName) { + anotherClass = this.schema.models[name]; + } + } + } + } + + var pluralized = i8n.pluralize(anotherClass.modelName); + var methodName = params.as || + i8n.camelize(pluralized, true); + var proxyMethodName = 'get' + i8n.titleize(pluralized, true); + + // create a proxy method + var fn = this.prototype[proxyMethodName] = function () { + // this[methodName] cannot be a shared method + // because it is defined inside + // a property getter... + + this[methodName].apply(thisClass, arguments); + }; + + fn.shared = true; + fn.http = {verb: 'get', path: '/' + methodName}; + fn.accepts = {arg: 'where', type: 'object'}; + hasMany.apply(this, arguments); + }; + } + + return ModelCtor; } return ds; } @@ -103,46 +149,6 @@ asteroid.createModel = function (name, properties, options) { var mb = new ModelBuilder(); var ModelCtor = mb.define(name, properties, arguments); - var hasMany = ModelCtor.hasMany; - - if(hasMany) { - ModelCtor.hasMany = function (anotherClass, params) { - var origArgs = arguments; - var thisClass = this, thisClassName = this.modelName; - params = params || {}; - if (typeof anotherClass === 'string') { - params.as = anotherClass; - if (params.model) { - anotherClass = params.model; - } else { - var anotherClassName = i8n.singularize(anotherClass).toLowerCase(); - for(var name in this.schema.models) { - if (name.toLowerCase() === anotherClassName) { - anotherClass = this.schema.models[name]; - } - } - } - } - - var pluralized = i8n.pluralize(anotherClass.modelName); - var methodName = params.as || - i8n.camelize(pluralized, true); - var proxyMethodName = 'get' + i8n.titleize(pluralized, true); - - // create a proxy method - var fn = this.prototype[proxyMethodName] = function () { - // this cannot be a shared method - // because it is defined when you - // inside a property getter... - - this[methodName].apply(thisClass, arguments); - }; - - fn.shared = true; - fn.http = {verb: 'get', path: '/' + methodName}; - hasMany.apply(this, arguments); - }; - } ModelCtor.shared = true; ModelCtor.sharedCtor = function (data, id, fn) { @@ -152,7 +158,13 @@ asteroid.createModel = function (name, properties, options) { id = null; } else if (typeof id === 'function') { fn = id; - id = null; + + if(typeof data !== 'object') { + id = data; + data = null; + } else { + id = null; + } } if(id && data) { @@ -180,20 +192,39 @@ asteroid.createModel = function (name, properties, options) { // before remote hook ModelCtor.beforeRemote = function (name, fn) { - var remotes = this.app.remotes(); - remotes.before(ModelCtor.pluralModelName + '.' + name, function (ctx, next) { - fn(ctx, ctx.instance, next); - }); + var self = this; + if(this.app) { + var remotes = this.app.remotes(); + remotes.before(self.pluralModelName + '.' + name, function (ctx, next) { + fn(ctx, ctx.instance, next); + }); + } else { + var args = arguments; + this._remoteHooks.once('attached', function () { + self.beforeRemote.apply(ModelCtor, args); + }); + } } // after remote hook ModelCtor.afterRemote = function (name, fn) { - var remotes = this.app.remotes(); - remotes.before(ModelCtor.pluralModelName + '.' + name, function (ctx, next) { - fn(ctx, ctx.instance, next); - }); + var self = this; + if(this.app) { + var remotes = this.app.remotes(); + remotes.after(self.pluralModelName + '.' + name, function (ctx, next) { + fn(ctx, ctx.instance, next); + }); + } else { + var args = arguments; + this._remoteHooks.once('attached', function () { + self.afterRemote.apply(ModelCtor, args); + }); + } } + // allow hooks to be added before attaching to an app + ModelCtor._remoteHooks = new EventEmitter(); + return ModelCtor; } @@ -205,8 +236,11 @@ asteroid.createModel = function (name, properties, options) { asteroid.remoteMethod = function (fn, options) { fn.shared = true; - Object.keys(options).forEach(function (key) { - fn[key] = options[key]; - }); + if(typeof options === 'object') { + Object.keys(options).forEach(function (key) { + fn[key] = options[key]; + }); + } + fn.http = fn.http || {verb: 'get'}; }