Merge pull request #1212 from strongloop/feature/app-registries

Per-app Models
This commit is contained in:
Ritchie Martori 2015-04-03 13:13:34 -07:00
commit 93960b838d
18 changed files with 415 additions and 120 deletions

View File

@ -375,6 +375,8 @@ module.exports = function(ACL) {
*/
ACL.checkAccessForContext = function(context, callback) {
var registry = this.registry;
if (!(context instanceof AccessContext)) {
context = new AccessContext(context);
}
@ -394,7 +396,7 @@ module.exports = function(ACL) {
var staticACLs = this.getStaticACLs(model.modelName, property);
var self = this;
var roleModel = loopback.getModelByType(Role);
var roleModel = registry.getModelByType(Role);
this.find({where: {model: model.modelName, property: propertyQuery,
accessType: accessTypeQuery}}, function(err, acls) {
if (err) {

View File

@ -24,9 +24,11 @@ module.exports = function(RoleMapping) {
* @param {Application} application
*/
RoleMapping.prototype.application = function(callback) {
var registry = this.constructor.registry;
if (this.principalType === RoleMapping.APPLICATION) {
var applicationModel = this.constructor.Application ||
loopback.getModelByType(loopback.Application);
registry.getModelByType('Application');
applicationModel.findById(this.principalId, callback);
} else {
process.nextTick(function() {
@ -42,9 +44,10 @@ module.exports = function(RoleMapping) {
* @param {User} user
*/
RoleMapping.prototype.user = function(callback) {
var RoleMapping = this.constructor;
if (this.principalType === RoleMapping.USER) {
var userModel = this.constructor.User ||
loopback.getModelByType(loopback.User);
var userModel = RoleMapping.User ||
RoleMapping.registry.getModelByType('User');
userModel.findById(this.principalId, callback);
} else {
process.nextTick(function() {
@ -60,9 +63,11 @@ module.exports = function(RoleMapping) {
* @param {User} childUser
*/
RoleMapping.prototype.childRole = function(callback) {
var registry = this.constructor.registry;
if (this.principalType === RoleMapping.ROLE) {
var roleModel = this.constructor.Role ||
loopback.getModelByType(loopback.Role);
registry.getModelByType(loopback.Role);
roleModel.findById(this.principalId, callback);
} else {
process.nextTick(function() {

View File

@ -29,7 +29,8 @@ module.exports = function(Role) {
// Set up the connection to users/applications/roles once the model
Role.once('dataSourceAttached', function() {
var roleMappingModel = this.RoleMapping || loopback.getModelByType(RoleMapping);
var registry = Role.registry;
var roleMappingModel = this.RoleMapping || registry.getModelByType(RoleMapping);
Role.prototype.users = function(callback) {
roleMappingModel.find({where: {roleId: this.id,
principalType: RoleMapping.USER}}, function(err, mappings) {
@ -242,6 +243,8 @@ module.exports = function(Role) {
context = new AccessContext(context);
}
var registry = this.registry;
debug('isInRole(): %s', role);
context.debug();
@ -277,7 +280,7 @@ module.exports = function(Role) {
return;
}
var roleMappingModel = this.RoleMapping || loopback.getModelByType(RoleMapping);
var roleMappingModel = this.RoleMapping || registry.getModelByType(RoleMapping);
this.findOne({where: {name: role}}, function(err, result) {
if (err) {
if (callback) callback(err);
@ -332,6 +335,7 @@ module.exports = function(Role) {
context = new AccessContext(context);
}
var roles = [];
var registry = this.registry;
var addRole = function(role) {
if (role && roles.indexOf(role) === -1) {
@ -358,7 +362,7 @@ module.exports = function(Role) {
});
});
var roleMappingModel = this.RoleMapping || loopback.getModelByType(RoleMapping);
var roleMappingModel = this.RoleMapping || registry.getModelByType(RoleMapping);
context.principals.forEach(function(p) {
// Check against the role mappings
var principalType = p.type || undefined;

View File

@ -25,6 +25,7 @@ module.exports = function(Scope) {
*/
Scope.checkPermission = function(scope, model, property, accessType, callback) {
var ACL = loopback.ACL;
var registry = this.registry;
assert(ACL,
'ACL model must be defined before Scope.checkPermission is called');
@ -32,7 +33,7 @@ module.exports = function(Scope) {
if (err) {
if (callback) callback(err);
} else {
var aclModel = loopback.getModelByType(ACL);
var aclModel = registry.getModelByType(ACL);
aclModel.checkPermission(ACL.SCOPE, scope.id, model, property, accessType, callback);
}
});

View File

@ -334,6 +334,7 @@ module.exports = function(User) {
User.prototype.verify = function(options, fn) {
var user = this;
var userModel = this.constructor;
var registry = userModel.registry;
assert(typeof options === 'object', 'options required when calling user.verify()');
assert(options.type, 'You must supply a verification type (options.type)');
assert(options.type === 'email', 'Unsupported verification type');
@ -364,7 +365,7 @@ module.exports = function(User) {
options.redirect;
// Email model
var Email = options.mailer || this.constructor.email || loopback.getModelByType(loopback.Email);
var Email = options.mailer || this.constructor.email || registry.getModelByType(loopback.Email);
// Set a default token generation function if one is not provided
var tokenGenerator = options.generateVerificationToken || User.generateVerificationToken;

View File

@ -3,7 +3,7 @@
*/
var DataSource = require('loopback-datasource-juggler').DataSource;
var registry = require('./registry');
var Registry = require('./registry');
var assert = require('assert');
var fs = require('fs');
var extend = require('util')._extend;
@ -104,6 +104,8 @@ app.disuse = function(route) {
app.model = function(Model, config) {
var isPublic = true;
var registry = this.registry;
if (arguments.length > 1) {
config = config || {};
if (typeof Model === 'string') {
@ -130,7 +132,7 @@ app.model = function(Model, config) {
configureModel(Model, config, this);
isPublic = config.public !== false;
} else {
assert(Model.prototype instanceof registry.Model,
assert(Model.prototype instanceof Model.registry.getModel('Model'),
Model.modelName + ' must be a descendant of loopback.Model');
}
@ -216,7 +218,7 @@ app.models = function() {
* @param {Object} config The data source config
*/
app.dataSource = function(name, config) {
var ds = dataSourcesFromConfig(config, this.connectors);
var ds = dataSourcesFromConfig(name, config, this.connectors, this.registry);
this.dataSources[name] =
this.dataSources[classify(name)] =
this.dataSources[camelize(name)] = ds;
@ -362,14 +364,14 @@ app.boot = function(options) {
'`app.boot` was removed, use the new module loopback-boot instead');
};
function dataSourcesFromConfig(config, connectorRegistry) {
function dataSourcesFromConfig(name, config, connectorRegistry, registry) {
var connectorPath;
assert(typeof config === 'object',
'cannont create data source without config object');
if (typeof config.connector === 'string') {
var name = config.connector;
name = config.connector;
if (connectorRegistry[name]) {
config.connector = connectorRegistry[name];
} else {
@ -385,7 +387,7 @@ function dataSourcesFromConfig(config, connectorRegistry) {
}
function configureModel(ModelCtor, config, app) {
assert(ModelCtor.prototype instanceof registry.Model,
assert(ModelCtor.prototype instanceof ModelCtor.registry.getModel('Model'),
ModelCtor.modelName + ' must be a descendant of loopback.Model');
var dataSource = config.dataSource;
@ -405,7 +407,7 @@ function configureModel(ModelCtor, config, app) {
config = extend({}, config);
config.dataSource = dataSource;
registry.configureModel(ModelCtor, config);
app.registry.configureModel(ModelCtor, config);
}
function clearHandlerCache(app) {

View File

@ -1,43 +1,43 @@
module.exports = function(loopback) {
module.exports = function(registry) {
// NOTE(bajtos) we must use static require() due to browserify limitations
loopback.Email = createModel(
registry.Email = createModel(
require('../common/models/email.json'),
require('../common/models/email.js'));
loopback.Application = createModel(
registry.Application = createModel(
require('../common/models/application.json'),
require('../common/models/application.js'));
loopback.AccessToken = createModel(
registry.AccessToken = createModel(
require('../common/models/access-token.json'),
require('../common/models/access-token.js'));
loopback.RoleMapping = createModel(
registry.RoleMapping = createModel(
require('../common/models/role-mapping.json'),
require('../common/models/role-mapping.js'));
loopback.Role = createModel(
registry.Role = createModel(
require('../common/models/role.json'),
require('../common/models/role.js'));
loopback.ACL = createModel(
registry.ACL = createModel(
require('../common/models/acl.json'),
require('../common/models/acl.js'));
loopback.Scope = createModel(
registry.Scope = createModel(
require('../common/models/scope.json'),
require('../common/models/scope.js'));
loopback.User = createModel(
registry.User = createModel(
require('../common/models/user.json'),
require('../common/models/user.js'));
loopback.Change = createModel(
registry.Change = createModel(
require('../common/models/change.json'),
require('../common/models/change.js'));
loopback.Checkpoint = createModel(
registry.Checkpoint = createModel(
require('../common/models/checkpoint.json'),
require('../common/models/checkpoint.js'));
@ -50,18 +50,18 @@ module.exports = function(loopback) {
MAIL: 'mail'
};
loopback.Email.autoAttach = dataSourceTypes.MAIL;
loopback.PersistedModel.autoAttach = dataSourceTypes.DB;
loopback.User.autoAttach = dataSourceTypes.DB;
loopback.AccessToken.autoAttach = dataSourceTypes.DB;
loopback.Role.autoAttach = dataSourceTypes.DB;
loopback.RoleMapping.autoAttach = dataSourceTypes.DB;
loopback.ACL.autoAttach = dataSourceTypes.DB;
loopback.Scope.autoAttach = dataSourceTypes.DB;
loopback.Application.autoAttach = dataSourceTypes.DB;
registry.Email.autoAttach = dataSourceTypes.MAIL;
registry.getModel('PersistedModel').autoAttach = dataSourceTypes.DB;
registry.User.autoAttach = dataSourceTypes.DB;
registry.AccessToken.autoAttach = dataSourceTypes.DB;
registry.Role.autoAttach = dataSourceTypes.DB;
registry.RoleMapping.autoAttach = dataSourceTypes.DB;
registry.ACL.autoAttach = dataSourceTypes.DB;
registry.Scope.autoAttach = dataSourceTypes.DB;
registry.Application.autoAttach = dataSourceTypes.DB;
function createModel(definitionJson, customizeFn) {
var Model = loopback.createModel(definitionJson);
var Model = registry.createModel(definitionJson);
customizeFn(Model);
return Model;
}

8
lib/global-registry.js Normal file
View File

@ -0,0 +1,8 @@
module.exports = function() {
var Registry = require('./registry');
var registry = global.__LOOPBACK_GLOBAL_REGISTRY__;
if (!registry) {
registry = global.__LOOPBACK_GLOBAL_REGISTRY__ = new Registry();
}
return registry;
};

View File

@ -10,6 +10,9 @@ var ejs = require('ejs');
var path = require('path');
var merge = require('util')._extend;
var assert = require('assert');
var Registry = require('./registry');
var getGlobalRegistry = require('./global-registry');
var juggler = require('loopback-datasource-juggler');
/**
* LoopBack core module. It provides static properties and
@ -25,6 +28,7 @@ var assert = require('assert');
* @property {String} mime
* @property {Boolean} isBrowser True if running in a browser environment; false otherwise. Static read-only property.
* @property {Boolean} isServer True if running in a server environment; false otherwise. Static read-only property.
* @property {Registry} registry The global `Registry` object.
* @property {String} faviconFile Path to a default favicon shipped with LoopBack.
* Use as follows: `app.use(require('serve-favicon')(loopback.faviconFile));`
* @class loopback
@ -45,6 +49,22 @@ loopback.version = require('../package.json').version;
loopback.mime = express.mime;
Object.defineProperty(loopback, 'registry', {
get: getGlobalRegistry
});
Object.defineProperty(loopback, 'Model', {
get: function() {
return this.registry.getModel('Model');
}
});
Object.defineProperty(loopback, 'PersistedModel', {
get: function() {
return this.registry.getModel('PersistedModel');
}
});
/*!
* Create an loopback application.
*
@ -52,7 +72,7 @@ loopback.mime = express.mime;
* @api public
*/
function createApplication() {
function createApplication(options) {
var app = loopbackExpress();
merge(app, proto);
@ -76,6 +96,13 @@ function createApplication() {
app.connector('memory', loopback.Memory);
app.connector('remote', loopback.Remote);
if (loopback.localRegistry || options && options.localRegistry === true) {
// setup the app registry
var registry = app.registry = new Registry();
} else {
app.registry = loopback.registry;
}
return app;
}
@ -91,7 +118,6 @@ function mixin(source) {
}
mixin(require('./runtime'));
mixin(require('./registry'));
/*!
* Expose static express methods like `express.errorHandler`.
@ -191,8 +217,198 @@ loopback.template = function(file) {
require('../server/current-context')(loopback);
/**
* Create a named vanilla JavaScript class constructor with an attached
* set of properties and options.
*
* This function comes with two variants:
* * `loopback.createModel(name, properties, options)`
* * `loopback.createModel(config)`
*
* In the second variant, the parameters `name`, `properties` and `options`
* are provided in the config object. Any additional config entries are
* interpreted as `options`, i.e. the following two configs are identical:
*
* ```js
* { name: 'Customer', base: 'User' }
* { name: 'Customer', options: { base: 'User' } }
* ```
*
* **Example**
*
* Create an `Author` model using the three-parameter variant:
*
* ```js
* loopback.createModel(
* 'Author',
* {
* firstName: 'string',
* lastName: 'string'
* },
* {
* relations: {
* books: {
* model: 'Book',
* type: 'hasAndBelongsToMany'
* }
* }
* }
* );
* ```
*
* Create the same model using a config object:
*
* ```js
* loopback.createModel({
* name: 'Author',
* properties: {
* firstName: 'string',
* lastName: 'string'
* },
* relations: {
* books: {
* model: 'Book',
* type: 'hasAndBelongsToMany'
* }
* }
* });
* ```
*
* @param {String} name Unique name.
* @param {Object} properties
* @param {Object} options (optional)
*
* @header loopback.createModel
*/
loopback.createModel = function(name, properties, options) {
return this.registry.createModel.apply(this.registry, arguments);
};
/**
* Alter an existing Model class.
* @param {Model} ModelCtor The model constructor to alter.
* @options {Object} config Additional configuration to apply
* @property {DataSource} dataSource Attach the model to a dataSource.
* @property {Object} [relations] Model relations to add/update.
*
* @header loopback.configureModel(ModelCtor, config)
*/
loopback.configureModel = function(ModelCtor, config) {
return this.registry.configureModel.apply(this.registry, arguments);
};
/**
* Look up a model class by name from all models created by
* `loopback.createModel()`
* @param {String} modelName The model name
* @returns {Model} The model class
*
* @header loopback.findModel(modelName)
*/
loopback.findModel = function(modelName) {
return this.registry.findModel.apply(this.registry, arguments);
};
/**
* Look up a model class by name from all models created by
* `loopback.createModel()`. Throw an error when no such model exists.
*
* @param {String} modelName The model name
* @returns {Model} The model class
*
* @header loopback.getModel(modelName)
*/
loopback.getModel = function(modelName) {
return this.registry.getModel.apply(this.registry, arguments);
};
/**
* Look up a model class by the base model class.
* The method can be used by LoopBack
* to find configured models in models.json over the base model.
* @param {Model} modelType The base model class
* @returns {Model} The subclass if found or the base class
*
* @header loopback.getModelByType(modelType)
*/
loopback.getModelByType = function(modelType) {
return this.registry.getModelByType.apply(this.registry, arguments);
};
/**
* Create a data source with passing the provided options to the connector.
*
* @param {String} name Optional name.
* @options {Object} options Data Source options
* @property {Object} connector LoopBack connector.
* @property {*} [*] Other connector properties.
* See the relevant connector documentation.
*/
loopback.createDataSource = function(name, options) {
return this.registry.createDataSource.apply(this.registry, arguments);
};
/**
* Get an in-memory data source. Use one if it already exists.
*
* @param {String} [name] The name of the data source.
* If not provided, the `'default'` is used.
*/
loopback.memory = function(name) {
return this.registry.memory.apply(this.registry, arguments);
};
/**
* Set the default `dataSource` for a given `type`.
* @param {String} type The datasource type.
* @param {Object|DataSource} dataSource The data source settings or instance
* @returns {DataSource} The data source instance.
*
* @header loopback.setDefaultDataSourceForType(type, dataSource)
*/
loopback.setDefaultDataSourceForType = function(type, dataSource) {
return this.registry.setDefaultDataSourceForType.apply(this.registry, arguments);
};
/**
* Get the default `dataSource` for a given `type`.
* @param {String} type The datasource type.
* @returns {DataSource} The data source instance
*/
loopback.getDefaultDataSourceForType = function(type) {
return this.registry.getDefaultDataSourceForType.apply(this.registry, arguments);
};
/**
* Attach any model that does not have a dataSource to
* the default dataSource for the type the Model requests
*/
loopback.autoAttach = function() {
return this.registry.autoAttach.apply(this.registry, arguments);
};
loopback.autoAttachModel = function(ModelCtor) {
return this.registry.autoAttachModel.apply(this.registry, arguments);
};
// temporary alias to simplify migration of code based on <=2.0.0-beta3
Object.defineProperty(loopback, 'DataModel', {
get: function() {
return this.registry.DataModel;
}
});
/*!
* Built in models / services
*/
require('./builtin-models')(loopback);
loopback.DataSource = juggler.DataSource;

View File

@ -95,15 +95,22 @@ module.exports = function(registry) {
* @property [{string}] settings.acls Array of ACLs for the model.
* @class
*/
var Model = registry.modelBuilder.define('Model');
Model.registry = registry;
/*!
* Called when a model is extended.
*/
Model.setup = function() {
var ModelCtor = this;
var Parent = this.super_;
if (!ModelCtor.registry && Parent && Parent.registry) {
ModelCtor.registry = Parent.registry;
}
var options = this.settings;
var typeName = this.modelName;
@ -250,6 +257,7 @@ module.exports = function(registry) {
*/
var _aclModel = null;
Model._ACL = function getACL(ACL) {
var registry = this.registry;
if (ACL !== undefined) {
// The function is used as a setter
_aclModel = ACL;

View File

@ -9,8 +9,7 @@ var deprecated = require('depd')('loopback');
var debug = require('debug')('loopback:persisted-model');
module.exports = function(registry) {
var Model = registry.Model;
var Model = registry.getModel('Model');
/**
* Extends Model with basic query and CRUD support.
@ -1401,7 +1400,7 @@ module.exports = function(registry) {
}
PersistedModel._defineChangeModel = function() {
var BaseChangeModel = registry.getModel('Change');
var BaseChangeModel = this.registry.getModel('Change');
assert(BaseChangeModel,
'Change model must be defined before enabling change replication');

View File

@ -1,12 +1,3 @@
/*
* This file exports methods and objects for manipulating
* Models and DataSources.
*
* It is an internal file that should not be used outside of loopback.
* All exported entities can be accessed via the `loopback` object.
* @private
*/
var assert = require('assert');
var extend = require('util')._extend;
var juggler = require('loopback-datasource-juggler');
@ -14,11 +5,23 @@ var debug = require('debug')('loopback:registry');
var DataSource = juggler.DataSource;
var ModelBuilder = juggler.ModelBuilder;
var registry = module.exports;
module.exports = Registry;
registry.defaultDataSources = {};
/**
* Define and reference `Models` and `DataSources`.
*
* @class
*/
registry.modelBuilder = new ModelBuilder();
function Registry() {
this.defaultDataSources = {};
this.modelBuilder = new ModelBuilder();
require('./model')(this);
require('./persisted-model')(this);
// Set the default model base class.
this.modelBuilder.defaultModelBaseClass = this.getModel('Model');
}
/**
* Create a named vanilla JavaScript class constructor with an attached
@ -84,7 +87,8 @@ registry.modelBuilder = new ModelBuilder();
* @header loopback.createModel
*/
registry.createModel = function(name, properties, options) {
Registry.prototype.createModel = function(name, properties, options) {
if (arguments.length === 1 && typeof name === 'object') {
var config = name;
name = config.name;
@ -106,7 +110,7 @@ registry.createModel = function(name, properties, options) {
if (baseName === 'DataModel') {
console.warn('Model `%s` is extending deprecated `DataModel. ' +
'Use `PeristedModel` instead.', name);
BaseModel = this.PersistedModel;
BaseModel = this.getModel('PeristedModel');
} else {
console.warn('Model `%s` is extending an unknown model `%s`. ' +
'Using `PersistedModel` as the base.', name, baseName);
@ -114,9 +118,9 @@ registry.createModel = function(name, properties, options) {
}
}
BaseModel = BaseModel || this.PersistedModel;
BaseModel = BaseModel || this.getModel('PersistedModel');
var model = BaseModel.extend(name, properties, options);
model.registry = this;
// try to attach
try {
@ -174,7 +178,7 @@ function addACL(acls, acl) {
* @header loopback.configureModel(ModelCtor, config)
*/
registry.configureModel = function(ModelCtor, config) {
Registry.prototype.configureModel = function(ModelCtor, config) {
var settings = ModelCtor.settings;
var modelName = ModelCtor.modelName;
@ -248,25 +252,26 @@ registry.configureModel = function(ModelCtor, config) {
/**
* Look up a model class by name from all models created by
* `loopback.createModel()`
* @param {String} modelName The model name
* @param {String|Function} modelOrName The model name or a `Model` constructor.
* @returns {Model} The model class
*
* @header loopback.findModel(modelName)
*/
registry.findModel = function(modelName) {
Registry.prototype.findModel = function(modelName) {
if (typeof modelType === 'function') return modelName;
return this.modelBuilder.models[modelName];
};
/**
* Look up a model class by name from all models created by
* `loopback.createModel()`. Throw an error when no such model exists.
* `loopback.createModel()`. **Throw an error when no such model exists.**
*
* @param {String} modelName The model name
* @param {String} modelOrName The model name or a `Model` constructor.
* @returns {Model} The model class
*
* @header loopback.getModel(modelName)
*/
registry.getModel = function(modelName) {
Registry.prototype.getModel = function(modelName) {
var model = this.findModel(modelName);
if (model) return model;
@ -282,9 +287,17 @@ registry.getModel = function(modelName) {
*
* @header loopback.getModelByType(modelType)
*/
registry.getModelByType = function(modelType) {
assert(typeof modelType === 'function',
'The model type must be a constructor');
Registry.prototype.getModelByType = function(modelType) {
var type = typeof modelType;
var accepted = ['function', 'string'];
assert(accepted.indexOf(type) > -1,
'The model type must be a constructor or model name');
if (type === 'string') {
modelType = this.getModel(modelType);
}
var models = this.modelBuilder.models;
for (var m in models) {
if (models[m].prototype instanceof modelType) {
@ -302,12 +315,11 @@ registry.getModelByType = function(modelType) {
* @property {Object} connector LoopBack connector.
* @property {*} [*] Other&nbsp;connector properties.
* See the relevant connector documentation.
*
* @header loopback.createDataSource(name, options)
*/
registry.createDataSource = function(name, options) {
Registry.prototype.createDataSource = function(name, options) {
var self = this;
var ds = new DataSource(name, options, self.modelBuilder);
ds.createModel = function(name, properties, settings) {
settings = settings || {};
@ -340,11 +352,9 @@ registry.createDataSource = function(name, options) {
*
* @param {String} [name] The name of the data source.
* If not provided, the `'default'` is used.
*
* @header loopback.memory([name])
*/
registry.memory = function(name) {
Registry.prototype.memory = function(name) {
name = name || 'default';
var memory = (
this._memoryDataSources || (this._memoryDataSources = {})
@ -368,7 +378,7 @@ registry.memory = function(name) {
* @header loopback.setDefaultDataSourceForType(type, dataSource)
*/
registry.setDefaultDataSourceForType = function(type, dataSource) {
Registry.prototype.setDefaultDataSourceForType = function(type, dataSource) {
var defaultDataSources = this.defaultDataSources;
if (!(dataSource instanceof DataSource)) {
@ -382,21 +392,19 @@ registry.setDefaultDataSourceForType = function(type, dataSource) {
/**
* Get the default `dataSource` for a given `type`.
* @param {String} type The datasource type.
* @returns {DataSource} The data source instance.
* @header loopback.getDefaultDataSourceForType(type)
* @returns {DataSource} The data source instance
*/
registry.getDefaultDataSourceForType = function(type) {
Registry.prototype.getDefaultDataSourceForType = function(type) {
return this.defaultDataSources && this.defaultDataSources[type];
};
/**
* Attach any model that does not have a dataSource to
* the default dataSource for the type the Model requests
* @header loopback.autoAttach()
*/
registry.autoAttach = function() {
Registry.prototype.autoAttach = function() {
var models = this.modelBuilder.models;
assert.equal(typeof models, 'object', 'Cannot autoAttach without a models object');
@ -410,7 +418,7 @@ registry.autoAttach = function() {
}, this);
};
registry.autoAttachModel = function(ModelCtor) {
Registry.prototype.autoAttachModel = function(ModelCtor) {
if (ModelCtor.autoAttach) {
var ds = this.getDefaultDataSourceForType(ModelCtor.autoAttach);
@ -424,18 +432,8 @@ registry.autoAttachModel = function(ModelCtor) {
}
};
registry.DataSource = DataSource;
/*
* Core models
* @private
*/
registry.Model = require('./model')(registry);
registry.PersistedModel = require('./persisted-model')(registry);
// temporary alias to simplify migration of code based on <=2.0.0-beta3
Object.defineProperty(registry, 'DataModel', {
Object.defineProperty(Registry.prototype, 'DataModel', {
get: function() {
var stackLines = new Error().stack.split('\n');
console.warn('loopback.DataModel is deprecated, ' +
@ -445,6 +443,3 @@ Object.defineProperty(registry, 'DataModel', {
return this.PersistedModel;
}
});
// Set the default model base class. This is done after the Model class is defined.
registry.modelBuilder.defaultModelBaseClass = registry.Model;

View File

@ -28,6 +28,7 @@ function rest() {
return function restApiHandler(req, res, next) {
var app = req.app;
var registry = app.registry;
// added for https://github.com/strongloop/loopback/issues/1134
if (app.get('legacyExplorer') !== false) {
@ -55,14 +56,8 @@ function rest() {
}
if (app.isAuthEnabled) {
// NOTE(bajtos) It would be better to search app.models for a model
// of type AccessToken instead of searching all loopback models.
// Unfortunately that's not supported now.
// Related discussions:
// https://github.com/strongloop/loopback/pull/167
// https://github.com/strongloop/loopback/commit/f07446a
var AccessToken = loopback.getModelByType(loopback.AccessToken);
handlers.push(loopback.token({ model: AccessToken }));
var AccessToken = registry.getModelByType('AccessToken');
handlers.push(loopback.token({ model: AccessToken, app: app }));
}
handlers.push(function(req, res, next) {

View File

@ -68,11 +68,8 @@ function escapeRegExp(str) {
function token(options) {
options = options || {};
var TokenModel = options.model || loopback.AccessToken;
if (typeof TokenModel === 'string') {
// Make it possible to configure the model in middleware.json
TokenModel = loopback.getModel(TokenModel);
}
var TokenModel;
var currentUserLiteral = options.currentUserLiteral;
if (currentUserLiteral && (typeof currentUserLiteral !== 'string')) {
debug('Set currentUserLiteral to \'me\' as the value is not a string.');
@ -81,10 +78,23 @@ function token(options) {
if (typeof currentUserLiteral === 'string') {
currentUserLiteral = escapeRegExp(currentUserLiteral);
}
assert(typeof TokenModel === 'function',
'loopback.token() middleware requires a AccessToken model');
return function(req, res, next) {
var app = req.app;
var registry = app.registry;
if (!TokenModel) {
if (registry === loopback.registry) {
TokenModel = options.model || loopback.AccessToken;
} else if (options.model) {
TokenModel = registry.getModel(options.model);
} else {
TokenModel = registry.getModel('AccessToken');
}
}
assert(typeof TokenModel === 'function',
'loopback.token() middleware requires a AccessToken model');
if (req.accessToken !== undefined) {
rewriteUserLiteral(req, currentUserLiteral);
return next();

View File

@ -352,7 +352,7 @@ function createTestApp(testToken, settings, done) {
var app = loopback();
app.use(loopback.cookieParser('secret'));
app.use(loopback.token({model: 'MyToken', currentUserLiteral: 'me'}));
app.use(loopback.token({model: Token, currentUserLiteral: 'me'}));
app.get('/token', function(req, res) {
res.cookie('authorization', testToken.id, {signed: true});
res.end();

View File

@ -628,7 +628,7 @@ describe('app', function() {
var Foo = app.models.foo;
var f = new Foo();
assert(f instanceof loopback.Model);
assert(f instanceof app.registry.getModel('Model'));
});
it('interprets extra first-level keys as options', function() {
@ -673,14 +673,15 @@ describe('app', function() {
describe('app.model(ModelCtor, config)', function() {
it('attaches the model to a datasource', function() {
var previousModel = loopback.registry.findModel('TestModel');
app.dataSource('db', { connector: 'memory' });
var TestModel = loopback.Model.extend('TestModel');
// TestModel was most likely already defined in a different test,
// thus TestModel.dataSource may be already set
delete TestModel.dataSource;
app.model(TestModel, { dataSource: 'db' });
if (previousModel) {
delete previousModel.dataSource;
}
assert(!previousModel || !previousModel.dataSource);
app.model('TestModel', { dataSource: 'db' });
expect(app.models.TestModel.dataSource).to.equal(app.dataSources.db);
});
});

View File

@ -60,8 +60,8 @@ describe('DataSource', function() {
});
var Color = ds.createModel('color', {name: String});
assert(Color.prototype instanceof loopback.Model);
assert.equal(Color.base, loopback.Model);
assert(Color.prototype instanceof Color.registry.getModel('Model'));
assert.equal(Color.base.modelName, 'PersistedModel');
});
});

48
test/registries.test.js Normal file
View File

@ -0,0 +1,48 @@
describe('Registry', function() {
describe('one per app', function() {
it('should allow two apps to reuse the same model name', function(done) {
var appFoo = loopback();
var appBar = loopback();
var modelName = 'MyModel';
var subModelName = 'Sub' + modelName;
var settings = {base: 'PersistedModel'};
appFoo.set('perAppRegistries', true);
appBar.set('perAppRegistries', true);
var dsFoo = appFoo.dataSource('dsFoo', {connector: 'memory'});
var dsBar = appFoo.dataSource('dsBar', {connector: 'memory'});
var FooModel = appFoo.model(modelName, settings);
var FooSubModel = appFoo.model(subModelName, settings);
var BarModel = appBar.model(modelName, settings);
var BarSubModel = appBar.model(subModelName, settings);
FooModel.attachTo(dsFoo);
FooSubModel.attachTo(dsFoo);
BarModel.attachTo(dsBar);
BarSubModel.attachTo(dsBar);
FooModel.hasMany(FooSubModel);
BarModel.hasMany(BarSubModel);
expect(appFoo.models[modelName]).to.not.equal(appBar.models[modelName]);
BarModel.create({name: 'bar'}, function(err, bar) {
assert(!err);
bar.subMyModels.create({parent: 'bar'}, function(err) {
assert(!err);
FooSubModel.find(function(err, foos) {
assert(!err);
expect(foos).to.eql([]);
BarSubModel.find(function(err, bars) {
assert(!err);
expect(bars.map(function(f) {
return f.parent;
})).to.eql(['bar']);
done();
});
});
});
});
});
});
});