Merge branch 'release/2.12.0' into production

This commit is contained in:
Raymond Feng 2014-12-08 16:19:10 -08:00
commit acd20ae6f8
10 changed files with 2982 additions and 16 deletions

2805
CHANGES.md Normal file

File diff suppressed because it is too large Load Diff

View File

@ -5,8 +5,9 @@ for interacting with databases, REST APIs, and other data sources. It was
initially forked from [JugglingDB](https://github.com/1602/jugglingdb).
**For full documentation, see the official StrongLoop documentation**:
* [Data sources and connectors](http://docs.strongloop.com/display/LB/Data+sources+and+connectors)
* [Creating data sources and connected models](http://docs.strongloop.com/display/LB/Creating+data+sources+and+connected+models).
[Connecting models to data sources](http://docs.strongloop.com/display/LB/Connecting+models+to+data+sources)
For information on creating data sources programmatically, see [Advanced topics: data sources](http://docs.strongloop.com/display/LB/Advanced+topics%3A+data+sources).
## Installation

View File

@ -1,10 +1,11 @@
{
"content": [
"lib/datasource.js",
"lib/geo.js",
"lib/model.js",
"lib/geo.js",
"lib/hooks.js",
"lib/include.js",
"lib/model-builder.js",
"lib/model-builder.js",
"lib/relations.js",
"lib/validations.js"
],

View File

@ -89,7 +89,7 @@ DataAccessObject.defaultScope = function(target, inst) {
DataAccessObject.applyScope = function(query, inst) {
var scope = this.defaultScope(query, inst) || {};
if (typeof scope === 'object') {
mergeQuery(query, scope || {}, this.definition.settings.scoping);
mergeQuery(query, scope || {}, this.definition.settings.scope);
}
};

View File

@ -1245,7 +1245,8 @@ DataSource.prototype.discoverSchemas = function (modelName, options, cb) {
var propName = fromDBName(item.columnName, true);
schema.properties[propName] = {
type: item.type,
required: (item.nullable === 'N'),
required: (item.nullable === 'N' || item.nullable === 'NO'
|| item.nullable === 0 || item.nullable === false),
length: item.dataLength,
precision: item.dataPrecision,
scale: item.dataScale

View File

@ -114,7 +114,7 @@ function RelationDefinition(definition) {
}
this.modelThrough = definition.modelThrough;
this.keyThrough = definition.keyThrough;
this.multiple = (this.type !== 'belongsTo' && this.type !== 'hasOne');
this.multiple = definition.multiple;
this.properties = definition.properties || {};
this.options = definition.options || {};
this.scope = definition.scope;
@ -1139,6 +1139,7 @@ RelationDefinition.belongsTo = function (modelFrom, modelTo, params) {
keyFrom: fk,
keyTo: idName,
modelTo: modelTo,
multiple: false,
properties: params.properties,
scope: params.scope,
options: params.options,
@ -1447,7 +1448,9 @@ RelationDefinition.hasOne = function (modelFrom, modelTo, params) {
keyFrom: pk,
keyTo: fk,
modelTo: modelTo,
multiple: false,
properties: params.properties,
scope: params.scope,
options: params.options,
polymorphic: polymorphic
});
@ -1750,7 +1753,7 @@ RelationDefinition.embedsOne = function (modelFrom, modelTo, params) {
configurable: true,
get: function() {
var relation = new EmbedsOne(definition, this);
var relationMethod = relation.related.bind(relation)
var relationMethod = relation.related.bind(relation);
relationMethod.create = relation.create.bind(relation);
relationMethod.build = relation.build.bind(relation);
relationMethod.update = relation.update.bind(relation);
@ -1794,7 +1797,10 @@ EmbedsOne.prototype.related = function (refresh, params) {
}
var embeddedInstance = modelInstance[propertyName];
embeddedInstance.__persisted = true;
if (embeddedInstance) {
embeddedInstance.__persisted = true;
}
if (typeof params === 'function') { // acts as async getter
var cb = params;

View File

@ -505,7 +505,7 @@ function validationFailed(inst, attr, conf, cb) {
// that can be specified in conf
if (skipValidation(inst, conf, 'if')
|| skipValidation(inst, conf, 'unless')) {
if (cb) cb(true);
if (cb) cb(false);
return false;
}

View File

@ -1,6 +1,6 @@
{
"name": "loopback-datasource-juggler",
"version": "2.11.0",
"version": "2.12.0",
"description": "LoopBack DataSoure Juggler",
"keywords": [
"StrongLoop",

View File

@ -1005,7 +1005,7 @@ describe('relations', function () {
author.avatar.create({ name: 'Avatar' }, function (err, p) {
should.not.exist(err);
should.exist(p);
p.oid.should.equal(author.username);
p.oid.toString().should.equal(author.username.toString());
p.type.should.equal('Author');
done();
});
@ -1017,7 +1017,7 @@ describe('relations', function () {
reader.mugshot.create({ name: 'Mugshot' }, function (err, p) {
should.not.exist(err);
should.exist(p);
p.oid.should.equal(reader.username);
p.oid.toString().should.equal(reader.username.toString());
p.type.should.equal('Reader');
done();
});
@ -1794,7 +1794,53 @@ describe('relations', function () {
done();
});
});
});
describe('hasOne with scope', function () {
var Supplier, Account;
var supplierId, accountId;
before(function () {
db = getSchema();
Supplier = db.define('Supplier', {name: String});
Account = db.define('Account', {accountNo: String, supplierName: String, block: Boolean});
Supplier.hasOne(Account, { scope: { where: { block: false } }, properties: { name: 'supplierName' } });
});
it('can be used to query data', function (done) {
db.automigrate(function () {
Supplier.create({name: 'Supplier 1'}, function (e, supplier) {
supplierId = supplier.id;
should.not.exist(e);
should.exist(supplier);
supplier.account.create({accountNo: 'a01'}, function (err, account) {
supplier.account(function (e, act) {
accountId = act.id;
should.not.exist(e);
should.exist(act);
act.should.be.an.instanceOf(Account);
supplier.account().id.should.equal(act.id);
act.supplierName.should.equal(supplier.name);
done();
});
});
});
});
});
it('should find record that match scope', function (done) {
Account.updateAll({ block: true }, function (err) {
Supplier.findById(supplierId, function (err, supplier) {
supplier.account(function (err, account) {
should.not.exists(account);
done();
});
});
});
});
});
describe('hasOne with non standard id', function () {
@ -1965,6 +2011,7 @@ describe('relations', function () {
describe('embedsOne', function () {
var person;
var Passport;
var Other;
before(function () {
@ -1975,6 +2022,7 @@ describe('relations', function () {
{name:{type:'string', required: true}},
{idInjection: false}
);
Address = tmp.define('Address', { street: String }, { idInjection: false });
Other = db.define('Other', {name: String});
});
@ -1982,6 +2030,7 @@ describe('relations', function () {
Person.embedsOne(Passport, {
default: {name: 'Anonymous'} // a bit contrived
});
Person.embedsOne(Address); // all by default
db.automigrate(done);
});
@ -1993,6 +2042,19 @@ describe('relations', function () {
p.passportItem.build.should.be.a.function;
p.passportItem.destroy.should.be.a.function;
});
it('should behave properly without default or being set', function (done) {
var p = new Person();
should.not.exist(p.address);
var a = p.addressItem();
should.not.exist(a);
Person.create({}, function (err, p) {
should.not.exist(p.address);
var a = p.addressItem();
should.not.exist(a);
done();
});
});
it('should return an instance with default values', function() {
var p = new Person();

View File

@ -52,16 +52,106 @@ describe('validations', function () {
describe('skipping', function () {
it('should allow to skip using if: attribute', function () {
it('should NOT skip when `if` is fulfilled', function () {
User.validatesPresenceOf('pendingPeriod', {if: 'createdByAdmin'});
var user = new User;
user.createdByAdmin = true;
user.isValid().should.be.false;
user.errors.pendingPeriod.should.eql(['can\'t be blank']);
user.pendingPeriod = 1
user.pendingPeriod = 1;
user.isValid().should.be.true;
});
it('should skip when `if` is NOT fulfilled', function () {
User.validatesPresenceOf('pendingPeriod', {if: 'createdByAdmin'});
var user = new User;
user.createdByAdmin = false;
user.isValid().should.be.true;
user.errors.should.be.false;
user.pendingPeriod = 1;
user.isValid().should.be.true;
});
it('should NOT skip when `unless` is fulfilled', function () {
User.validatesPresenceOf('pendingPeriod', {unless: 'createdByAdmin'});
var user = new User;
user.createdByAdmin = false;
user.isValid().should.be.false;
user.errors.pendingPeriod.should.eql(['can\'t be blank']);
user.pendingPeriod = 1;
user.isValid().should.be.true;
});
it('should skip when `unless` is NOT fulfilled', function () {
User.validatesPresenceOf('pendingPeriod', {unless: 'createdByAdmin'});
var user = new User;
user.createdByAdmin = true;
user.isValid().should.be.true;
user.errors.should.be.false;
user.pendingPeriod = 1;
user.isValid().should.be.true;
});
});
describe('skipping in async validation', function () {
it('should skip when `if` is NOT fulfilled', function (done) {
User.validateAsync('pendingPeriod', function (err, done) {
if (!this.pendingPeriod) err();
done();
}, {if: 'createdByAdmin', code: 'presence', message: 'can\'t be blank'});
var user = new User;
user.createdByAdmin = false;
user.isValid(function (valid) {
valid.should.be.true;
user.errors.should.be.false;
done();
});
});
it('should NOT skip when `if` is fulfilled', function (done) {
User.validateAsync('pendingPeriod', function (err, done) {
if (!this.pendingPeriod) err();
done();
}, {if: 'createdByAdmin', code: 'presence', message: 'can\'t be blank'});
var user = new User;
user.createdByAdmin = true;
user.isValid(function (valid) {
valid.should.be.false;
user.errors.pendingPeriod.should.eql(['can\'t be blank']);
done();
});
});
it('should skip when `unless` is NOT fulfilled', function (done) {
User.validateAsync('pendingPeriod', function (err, done) {
if (!this.pendingPeriod) err();
done();
}, {unless: 'createdByAdmin', code: 'presence', message: 'can\'t be blank'});
var user = new User;
user.createdByAdmin = true;
user.isValid(function (valid) {
valid.should.be.true;
user.errors.should.be.false;
done();
});
});
it('should NOT skip when `unless` is fulfilled', function (done) {
User.validateAsync('pendingPeriod', function (err, done) {
if (!this.pendingPeriod) err();
done();
}, {unless: 'createdByAdmin', code: 'presence', message: 'can\'t be blank'});
var user = new User;
user.createdByAdmin = false;
user.isValid(function (valid) {
valid.should.be.false;
user.errors.pendingPeriod.should.eql(['can\'t be blank']);
done();
});
});
});
describe('lifecycle', function () {