Add support for app level Model isolation
- `loopback.registry` is now a true global registry - `app.registry` is unique per app object - `Model.registry` is set when a Model is created using any registry method - `loopback.localRegistry` and `loopback({localRegistry: true})` when set to `true` this will create a `Registry` per `Application`. It defaults to `false`.
This commit is contained in:
parent
5a51c7f0fa
commit
b9170751bc
|
@ -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) {
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
220
lib/loopback.js
220
lib/loopback.js
|
@ -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;
|
||||
|
|
10
lib/model.js
10
lib/model.js
|
@ -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;
|
||||
|
|
|
@ -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');
|
||||
|
||||
|
|
|
@ -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 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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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');
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue