Merge branch 'release/1.2.11' into production
This commit is contained in:
commit
4982858819
|
@ -363,18 +363,10 @@ DataSource.prototype.defineRelations = function(modelClass, relations) {
|
||||||
for (var rn in relations) {
|
for (var rn in relations) {
|
||||||
var r = relations[rn];
|
var r = relations[rn];
|
||||||
assert(DataSource.relationTypes.indexOf(r.type) !== -1, "Invalid relation type: " + r.type);
|
assert(DataSource.relationTypes.indexOf(r.type) !== -1, "Invalid relation type: " + r.type);
|
||||||
var targetModel = isModelClass(r.model) ? r.model : this.getModel(r.model);
|
var targetModel = isModelClass(r.model) ? r.model : this.getModel(r.model, true);
|
||||||
if (!targetModel) {
|
|
||||||
// The target model doesn't exist, let create a place holder for it
|
|
||||||
targetModel = this.define(r.model, {}, {unresolved: true});
|
|
||||||
}
|
|
||||||
var throughModel = null;
|
var throughModel = null;
|
||||||
if (r.through) {
|
if (r.through) {
|
||||||
throughModel = isModelClass(r.through) ? r.through : this.getModel(r.through);
|
throughModel = isModelClass(r.through) ? r.through : this.getModel(r.through, true);
|
||||||
if (!throughModel) {
|
|
||||||
// The through model doesn't exist, let create a place holder for it
|
|
||||||
throughModel = this.define(r.through, {}, {unresolved: true});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (!isModelDataSourceAttached(targetModel) || (throughModel && !isModelDataSourceAttached(throughModel))) {
|
if (!isModelDataSourceAttached(targetModel) || (throughModel && !isModelDataSourceAttached(throughModel))) {
|
||||||
// Create a listener to defer the relation set up
|
// Create a listener to defer the relation set up
|
||||||
|
@ -533,8 +525,8 @@ DataSource.prototype.mixin = function (ModelCtor) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
DataSource.prototype.getModel = function(name) {
|
DataSource.prototype.getModel = function(name, forceCreate) {
|
||||||
return this.modelBuilder.getModel(name);
|
return this.modelBuilder.getModel(name, forceCreate);
|
||||||
};
|
};
|
||||||
|
|
||||||
DataSource.prototype.getModelDefinition = function(name) {
|
DataSource.prototype.getModelDefinition = function(name) {
|
||||||
|
|
|
@ -56,8 +56,19 @@ function isModelClass(cls) {
|
||||||
return cls.prototype instanceof DefaultModelBaseClass;
|
return cls.prototype instanceof DefaultModelBaseClass;
|
||||||
}
|
}
|
||||||
|
|
||||||
ModelBuilder.prototype.getModel = function(name) {
|
/**
|
||||||
return this.models[name];
|
* Get a model by name
|
||||||
|
* @param {String} name The model name
|
||||||
|
* @param {Boolean} forceCreate Indicate if a stub should be created for the
|
||||||
|
* given name if a model doesn't exist
|
||||||
|
* @returns {*} The model class
|
||||||
|
*/
|
||||||
|
ModelBuilder.prototype.getModel = function(name, forceCreate) {
|
||||||
|
var model = this.models[name];
|
||||||
|
if(!model && forceCreate) {
|
||||||
|
model = this.define(name, {}, {unresolved: true});
|
||||||
|
}
|
||||||
|
return model;
|
||||||
};
|
};
|
||||||
|
|
||||||
ModelBuilder.prototype.getModelDefinition = function(name) {
|
ModelBuilder.prototype.getModelDefinition = function(name) {
|
||||||
|
@ -186,6 +197,14 @@ ModelBuilder.prototype.define = function defineClass(className, properties, sett
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load and inject the model classes
|
||||||
|
if(settings.models) {
|
||||||
|
Object.keys(settings.models).forEach(function(m) {
|
||||||
|
var model = settings.models[m];
|
||||||
|
ModelClass[m] = typeof model === 'string' ? modelBuilder.getModel(model, true) : model;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
ModelClass.getter = {};
|
ModelClass.getter = {};
|
||||||
ModelClass.setter = {};
|
ModelClass.setter = {};
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,7 @@ Relation.relationNameFor = function relationNameFor(foreignKey) {
|
||||||
* @example `User.hasMany(Post, {as: 'posts', foreignKey: 'authorId'});`
|
* @example `User.hasMany(Post, {as: 'posts', foreignKey: 'authorId'});`
|
||||||
*/
|
*/
|
||||||
Relation.hasMany = function hasMany(anotherClass, params) {
|
Relation.hasMany = function hasMany(anotherClass, params) {
|
||||||
var thisClass = this, thisClassName = this.modelName;
|
var thisClassName = this.modelName;
|
||||||
params = params || {};
|
params = params || {};
|
||||||
if (typeof anotherClass === 'string') {
|
if (typeof anotherClass === 'string') {
|
||||||
params.as = anotherClass;
|
params.as = anotherClass;
|
||||||
|
@ -71,7 +71,6 @@ Relation.hasMany = function hasMany(anotherClass, params) {
|
||||||
done = function() {};
|
done = function() {};
|
||||||
}
|
}
|
||||||
var self = this;
|
var self = this;
|
||||||
var id = this[idName];
|
|
||||||
anotherClass.create(data, function(err, ac) {
|
anotherClass.create(data, function(err, ac) {
|
||||||
if (err) return done(err, ac);
|
if (err) return done(err, ac);
|
||||||
var d = {};
|
var d = {};
|
||||||
|
@ -98,7 +97,6 @@ Relation.hasMany = function hasMany(anotherClass, params) {
|
||||||
params.through.findOrCreate({where: query}, data, done);
|
params.through.findOrCreate({where: query}, data, done);
|
||||||
};
|
};
|
||||||
scopeMethods.remove = function(acInst, done) {
|
scopeMethods.remove = function(acInst, done) {
|
||||||
var self = this;
|
|
||||||
var q = {};
|
var q = {};
|
||||||
q[fk2] = acInst[idName] || acInst;
|
q[fk2] = acInst[idName] || acInst;
|
||||||
params.through.findOne({where: q}, function(err, d) {
|
params.through.findOne({where: q}, function(err, d) {
|
||||||
|
|
22
lib/scope.js
22
lib/scope.js
|
@ -24,6 +24,17 @@ function defineScope(cls, targetClass, name, params, methods) {
|
||||||
Object.defineProperty(cls, name, {
|
Object.defineProperty(cls, name, {
|
||||||
enumerable: false,
|
enumerable: false,
|
||||||
configurable: true,
|
configurable: true,
|
||||||
|
/**
|
||||||
|
* This defines a property for the scope. For example, user.accounts or
|
||||||
|
* User.vips. Please note the cls can be the model class or prototype of
|
||||||
|
* the model class.
|
||||||
|
*
|
||||||
|
* The property value is function. It can be used to query the scope,
|
||||||
|
* such as user.accounts(condOrRefresh, cb) or User.vips(cb). The value
|
||||||
|
* can also have child properties for create/build/delete. For example,
|
||||||
|
* user.accounts.create(act, cb).
|
||||||
|
*
|
||||||
|
*/
|
||||||
get: function () {
|
get: function () {
|
||||||
var f = function caller(condOrRefresh, cb) {
|
var f = function caller(condOrRefresh, cb) {
|
||||||
var actualCond = {};
|
var actualCond = {};
|
||||||
|
@ -60,6 +71,7 @@ function defineScope(cls, targetClass, name, params, methods) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
f._scope = typeof params === 'function' ? params.call(this) : params;
|
f._scope = typeof params === 'function' ? params.call(this) : params;
|
||||||
|
|
||||||
f.build = build;
|
f.build = build;
|
||||||
f.create = create;
|
f.create = create;
|
||||||
f.destroyAll = destroyAll;
|
f.destroyAll = destroyAll;
|
||||||
|
@ -83,8 +95,10 @@ function defineScope(cls, targetClass, name, params, methods) {
|
||||||
|
|
||||||
// Wrap the property into a function for remoting
|
// Wrap the property into a function for remoting
|
||||||
var fn = function() {
|
var fn = function() {
|
||||||
|
// primaryObject.scopeName, such as user.accounts
|
||||||
var f = this[name];
|
var f = this[name];
|
||||||
f.apply(this, arguments);
|
// set receiver to be the scope property whose value is a function
|
||||||
|
f.apply(this[name], arguments);
|
||||||
};
|
};
|
||||||
|
|
||||||
fn.shared = true;
|
fn.shared = true;
|
||||||
|
@ -97,12 +111,12 @@ function defineScope(cls, targetClass, name, params, methods) {
|
||||||
|
|
||||||
var fn_create = function() {
|
var fn_create = function() {
|
||||||
var f = this[name].create;
|
var f = this[name].create;
|
||||||
f.apply(this, arguments);
|
f.apply(this[name], arguments);
|
||||||
};
|
};
|
||||||
|
|
||||||
fn_create.shared = true;
|
fn_create.shared = true;
|
||||||
fn_create.http = {verb: 'post', path: '/' + name};
|
fn_create.http = {verb: 'post', path: '/' + name};
|
||||||
fn_create.accepts = {arg: 'data', type: 'object', source: 'body'};
|
fn_create.accepts = {arg: 'data', type: 'object', http: {source: 'body'}};
|
||||||
fn_create.description = 'Creates ' + name;
|
fn_create.description = 'Creates ' + name;
|
||||||
fn_create.returns = {arg: 'data', type: 'object', root: true};
|
fn_create.returns = {arg: 'data', type: 'object', root: true};
|
||||||
|
|
||||||
|
@ -110,7 +124,7 @@ function defineScope(cls, targetClass, name, params, methods) {
|
||||||
|
|
||||||
var fn_delete = function() {
|
var fn_delete = function() {
|
||||||
var f = this[name].destroyAll;
|
var f = this[name].destroyAll;
|
||||||
f.apply(this, arguments);
|
f.apply(this[name], arguments);
|
||||||
};
|
};
|
||||||
fn_delete.shared = true;
|
fn_delete.shared = true;
|
||||||
fn_delete.http = {verb: 'delete', path: '/' + name};
|
fn_delete.http = {verb: 'delete', path: '/' + name};
|
||||||
|
|
10
lib/utils.js
10
lib/utils.js
|
@ -147,14 +147,10 @@ function mergeSettings(target, src) {
|
||||||
if (array) {
|
if (array) {
|
||||||
target = target || [];
|
target = target || [];
|
||||||
dst = dst.concat(target);
|
dst = dst.concat(target);
|
||||||
src.forEach(function (e, i) {
|
src.forEach(function (e) {
|
||||||
if (typeof target[i] === 'undefined') {
|
if (dst.indexOf(e) === -1) {
|
||||||
dst[i] = e;
|
|
||||||
} else {
|
|
||||||
if (target.indexOf(e) === -1) {
|
|
||||||
dst.push(e);
|
dst.push(e);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
if (target && typeof target === 'object') {
|
if (target && typeof target === 'object') {
|
||||||
|
@ -170,7 +166,7 @@ function mergeSettings(target, src) {
|
||||||
if (!target[key]) {
|
if (!target[key]) {
|
||||||
dst[key] = src[key]
|
dst[key] = src[key]
|
||||||
} else {
|
} else {
|
||||||
dst[key] = mergeSettings(target[key], src[key])
|
dst[key] = mergeSettings(target[key], src[key]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "loopback-datasource-juggler",
|
"name": "loopback-datasource-juggler",
|
||||||
"version": "1.2.10",
|
"version": "1.2.11",
|
||||||
"description": "LoopBack DataSoure Juggler",
|
"description": "LoopBack DataSoure Juggler",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"StrongLoop",
|
"StrongLoop",
|
||||||
|
|
|
@ -906,3 +906,40 @@ describe('Injected methods from connectors', function(){
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
describe('ModelBuilder options.models', function(){
|
||||||
|
it('should inject model classes from models', function() {
|
||||||
|
var builder = new ModelBuilder();
|
||||||
|
var M1 = builder.define('M1');
|
||||||
|
var M2 = builder.define('M2', {}, {models: {
|
||||||
|
'M1': M1
|
||||||
|
}});
|
||||||
|
|
||||||
|
assert.equal(M2.M1, M1, 'M1 should be injected to M2');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should inject model classes by name in the models', function() {
|
||||||
|
var builder = new ModelBuilder();
|
||||||
|
var M1 = builder.define('M1');
|
||||||
|
var M2 = builder.define('M2', {}, {models: {
|
||||||
|
'M1': 'M1'
|
||||||
|
}});
|
||||||
|
|
||||||
|
assert.equal(M2.M1, M1, 'M1 should be injected to M2');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should inject model classes by name in the models before the class is defined',
|
||||||
|
function() {
|
||||||
|
var builder = new ModelBuilder();
|
||||||
|
var M2 = builder.define('M2', {}, {models: {
|
||||||
|
'M1': 'M1'
|
||||||
|
}});
|
||||||
|
assert(M2.M1, 'M1 should be injected to M2');
|
||||||
|
assert(M2.M1.settings.unresolved, 'M1 is still a proxy');
|
||||||
|
var M1 = builder.define('M1');
|
||||||
|
assert.equal(M2.M1, M1, 'M1 should be injected to M2');
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ var should = require('./init.js');
|
||||||
var utils = require('../lib/utils');
|
var utils = require('../lib/utils');
|
||||||
var fieldsToArray = utils.fieldsToArray;
|
var fieldsToArray = utils.fieldsToArray;
|
||||||
var removeUndefined = utils.removeUndefined;
|
var removeUndefined = utils.removeUndefined;
|
||||||
|
var mergeSettings = utils.mergeSettings;
|
||||||
|
|
||||||
|
|
||||||
describe('util.fieldsToArray', function(){
|
describe('util.fieldsToArray', function(){
|
||||||
|
@ -115,3 +116,75 @@ describe('util.parseSettings', function(){
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('mergeSettings', function () {
|
||||||
|
it('should merge settings correctly', function () {
|
||||||
|
var src = { base: 'User',
|
||||||
|
relations: { accessTokens: { model: 'accessToken', type: 'hasMany',
|
||||||
|
foreignKey: 'userId' },
|
||||||
|
account: { model: 'account', type: 'belongsTo' } },
|
||||||
|
acls: [
|
||||||
|
{ accessType: '*',
|
||||||
|
permission: 'DENY',
|
||||||
|
principalType: 'ROLE',
|
||||||
|
principalId: '$everyone' },
|
||||||
|
{ accessType: '*',
|
||||||
|
permission: 'ALLOW',
|
||||||
|
principalType: 'ROLE',
|
||||||
|
property: 'login',
|
||||||
|
principalId: '$everyone' },
|
||||||
|
{ permission: 'ALLOW',
|
||||||
|
property: 'findById',
|
||||||
|
principalType: 'ROLE',
|
||||||
|
principalId: '$owner' }
|
||||||
|
] };
|
||||||
|
var tgt = { strict: false,
|
||||||
|
acls: [
|
||||||
|
{ principalType: 'ROLE',
|
||||||
|
principalId: '$everyone',
|
||||||
|
permission: 'ALLOW',
|
||||||
|
property: 'create' },
|
||||||
|
{ principalType: 'ROLE',
|
||||||
|
principalId: '$owner',
|
||||||
|
permission: 'ALLOW',
|
||||||
|
property: 'removeById' }
|
||||||
|
],
|
||||||
|
maxTTL: 31556926,
|
||||||
|
ttl: 1209600 };
|
||||||
|
|
||||||
|
var dst = mergeSettings(tgt, src);
|
||||||
|
|
||||||
|
var expected = { strict: false,
|
||||||
|
acls: [
|
||||||
|
{ principalType: 'ROLE',
|
||||||
|
principalId: '$everyone',
|
||||||
|
permission: 'ALLOW',
|
||||||
|
property: 'create' },
|
||||||
|
{ principalType: 'ROLE',
|
||||||
|
principalId: '$owner',
|
||||||
|
permission: 'ALLOW',
|
||||||
|
property: 'removeById' },
|
||||||
|
{ accessType: '*',
|
||||||
|
permission: 'DENY',
|
||||||
|
principalType: 'ROLE',
|
||||||
|
principalId: '$everyone' },
|
||||||
|
{ accessType: '*',
|
||||||
|
permission: 'ALLOW',
|
||||||
|
principalType: 'ROLE',
|
||||||
|
property: 'login',
|
||||||
|
principalId: '$everyone' },
|
||||||
|
{ permission: 'ALLOW',
|
||||||
|
property: 'findById',
|
||||||
|
principalType: 'ROLE',
|
||||||
|
principalId: '$owner' }
|
||||||
|
],
|
||||||
|
maxTTL: 31556926,
|
||||||
|
ttl: 1209600,
|
||||||
|
base: 'User',
|
||||||
|
relations: { accessTokens: { model: 'accessToken', type: 'hasMany',
|
||||||
|
foreignKey: 'userId' },
|
||||||
|
account: { model: 'account', type: 'belongsTo' } } };
|
||||||
|
|
||||||
|
should.deepEqual(dst.acls, expected.acls, 'Merged settings should match the expectation');
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in New Issue