Merge branch 'release/2.13.0' into production

This commit is contained in:
Raymond Feng 2015-01-07 13:58:08 -08:00
commit dfd26e4c23
11 changed files with 303 additions and 96 deletions

View File

@ -1,4 +1,42 @@
2014-12-09, Version 2.12.0
2015-01-07, Version 2.13.0
==========================
* added test for sorting undefined values (Christian Vette)
* Fix the floating number comparison (Raymond Feng)
* Fix bad CLA URL in CONTRIBUTING.md (Ryan Graham)
* replace deprecated function __defineGetter__ (bitmage)
* add a flag to callback of findOrCreate to indicate find or create (Clark Wang)
* fix sorting of undefined values with multiple columns (Christian Vette)
* code style (cvette)
* fix sorting with undefined in memory connector (cvette)
* Added support for inline parameters like: new GeoPoint(-34, 150) (Simo Moujami)
* fix default include in default scope fails findById (Clark Wang)
* Added test for toString() (Simo Moujami)
* Additional formatting (Simo Moujami)
* Fixed constructor parameters and added bdd tests for constructor validation (Simo Moujami)
* Fixed indentation (Simo Moujami)
* Added mocha tests for GeoPoint (Simo Moujami)
* renamed intermediary variable (Simo Moujami)
* Fixed the haversine formula to calculate distance between 2 points properly (Simo Moujami)
2014-12-08, Version 2.12.0
==========================
* Relax the id comparison (Raymond Feng)
@ -391,19 +429,16 @@
* Cleanup mixin tests (Fabien Franzen)
* Fix a name conflict in scope metadata (Raymond Feng)
2014-08-08, Version 2.3.0
=========================
2014-08-08, Version 2.3.1
=========================
* Fix a name conflict in scope metadata (Raymond Feng)
2014-08-08, Version 2.3.0
=========================
* Fix the test case so that it works with other DBs (Raymond Feng)
* Bump version (Raymond Feng)
@ -516,6 +551,8 @@
* Implemented embedsMany relation (Fabien Franzen)
* Fix a regression where undefined id should not match any record (Raymond Feng)
* Minor tweaks; pass-through properties/scope for hasAndBelongsToMany (Fabien Franzen)
* Implemented polymorphic hasMany through inverse (Fabien Franzen)
@ -531,6 +568,11 @@
* Implemented polymorphic hasMany (Fabien Franzen)
2014-07-27, Version 2.1.0
=========================
2014-07-27, Version 2.1.1
=========================
@ -538,12 +580,6 @@
* Fix a regression where undefined id should not match any record (Raymond Feng)
2014-07-27, Version 2.1.0
=========================
* Bump version (Raymond Feng)
* datasource: support connectors without `getTypes` (Miroslav Bajtoš)
* relation: add `scope._target` for `hasOne` (Miroslav Bajtoš)
@ -628,6 +664,10 @@
* Add missing inflection dep back (Raymond Feng)
2014-07-15, Version 2.0.0-beta3
===============================
* Bump version (Raymond Feng)
* 2.0.0-beta2 (Miroslav Bajtoš)
@ -664,17 +704,12 @@
2014-07-15, Version 1.7.0
=========================
2014-07-15, Version 2.0.0-beta3
===============================
* Bump version (Raymond Feng)
* Make sure related properties are defined for RDBMS (Raymond Feng)
* Test instance or id by the model type (Raymond Feng)
* Bump version (Raymond Feng)
* Allow before hooks to pass arguments to next() (Raymond Feng)
* Remoting methods for hasMany through (Raymond Feng)
@ -699,36 +734,6 @@
* DAO.prototype.exists should return 'boolean' type. (Samuel Reed)
* 2.0.0-beta2 (Miroslav Bajtoš)
* validations: support non-V8 browsers (Miroslav Bajtoš)
* Remove remoting metadata (Raymond Feng)
* Fix the forEach closure (Raymond Feng)
* ModelBuilder: add `prototype.defineValueType` (Miroslav Bajtoš)
* Replace connector base with loopback-connector (Miroslav Bajtoš)
* Remove unsupported connectors (Miroslav Bajtoš)
* 2.0.0-beta1 (Ritchie Martori)
* Keep undefined/null values for the array type (Raymond Feng)
* Remove JSDocs for scopeMethods.add(acInst) and scopeMethods.remove(acInst) (crandmck)
* Copy info from api-model.md to JSDoc (crandmck)
* !fixup Remove additional remoting (Ritchie Martori)
* !fixup Require ._delegate for fn override (Ritchie Martori)
* Remove relation remoting (Ritchie Martori)
* Remove remoting metadata (Ritchie Martori)
2014-07-03, Version 1.6.3
=========================
@ -1091,8 +1096,6 @@
* Simplify the test case (Raymond Feng)
* Revert the inflection version due to regression in camelize (Raymond Feng)
* Add unit test for datatype handling in updateAttributes. (arlaneenalra)
* Move new var into thunk. (arlaneenalra)
@ -1100,11 +1103,6 @@
* Use type converted data when writing back to database. (arlaneenalra)
2014-02-11, Version 1.3.0
=========================
2014-02-11, Version 1.3.1
=========================
@ -1112,6 +1110,10 @@
* Revert the inflection version due to regression in camelize (Raymond Feng)
2014-02-11, Version 1.3.0
=========================
* Bump version and update deps (Raymond Feng)
* Add a test case (Raymond Feng)

View File

@ -14,7 +14,7 @@ Contributing to `loopback-datasource-juggler` is easy. In a few simple steps:
* Adhere to code style outlined in the [Google C++ Style Guide][] and
[Google Javascript Style Guide][].
* Sign the [Contributor License Agreement](https://cla.strongloop.com/strongloop/loopback-datasource-juggler)
* Sign the [Contributor License Agreement](https://cla.strongloop.com/agreements/strongloop/loopback-datasource-juggler)
* Submit a pull request through Github.

View File

@ -4,11 +4,11 @@ exports.ModelBaseClass = require('./lib/model.js');
exports.GeoPoint = require('./lib/geo.js').GeoPoint;
exports.ValidationError = require('./lib/validations.js').ValidationError;
exports.__defineGetter__('version', function () {
return require('./package.json').version;
Object.defineProperty(exports, 'version', {
get: function() {return require('./package.json').version;}
});
var commonTest = './test/common_test';
exports.__defineGetter__('test', function () {
return require(commonTest);
Object.defineProperty(exports, 'test', {
get: function() {return require(commonTest);}
});

View File

@ -347,13 +347,19 @@ Memory.prototype.all = function all(model, filter, callback) {
});
function sorting(a, b) {
var undefinedA, undefinedB;
for (var i = 0, l = this.length; i < l; i++) {
if (a[this[i].key] > b[this[i].key]) {
undefinedB = b[this[i].key] === undefined && a[this[i].key] !== undefined;
undefinedA = a[this[i].key] === undefined && b[this[i].key] !== undefined;
if (undefinedB || a[this[i].key] > b[this[i].key]) {
return 1 * this[i].reverse;
} else if (a[this[i].key] < b[this[i].key]) {
} else if (undefinedA || a[this[i].key] < b[this[i].key]) {
return -1 * this[i].reverse;
}
}
return 0;
}
};

View File

@ -58,7 +58,6 @@ function byIdQuery(m, id) {
var pk = idName(m);
var query = { where: {} };
query.where[pk] = id;
m.applyScope(query);
return query;
}
@ -317,7 +316,7 @@ DataAccessObject.updateOrCreate = DataAccessObject.upsert = function upsert(data
* @param {Object} query Search conditions. See [find](#dataaccessobjectfindquery-callback) for query format.
* For example: `{where: {test: 'me'}}`.
* @param {Object} data Object to create.
* @param {Function} cb Callback called with (err, instance)
* @param {Function} cb Callback called with (err, instance, created)
*/
DataAccessObject.findOrCreate = function findOrCreate(query, data, callback) {
if (query === undefined) {
@ -335,8 +334,10 @@ DataAccessObject.findOrCreate = function findOrCreate(query, data, callback) {
var t = this;
this.findOne(query, function (err, record) {
if (err) return callback(err);
if (record) return callback(null, record);
t.create(data, callback);
if (record) return callback(null, record, false);
t.create(data, function (err, record) {
callback(err, record, record != null);
});
});
};

View File

@ -111,7 +111,11 @@ exports.GeoPoint = GeoPoint;
*
* @options {Object} Options Object with two Number properties: lat and long.
* @property {Number} lat The latitude point in degrees. Range: -90 to 90.
* @property {Number} lng The longitude point in degrees. Range: -90 to 90.
* @property {Number} lng The longitude point in degrees. Range: -180 to 180.
*
* @options {Array} Options Array with two Number entries: [lat,long].
* @property {Number} lat The latitude point in degrees. Range: -90 to 90.
* @property {Number} lng The longitude point in degrees. Range: -180 to 180.
*/
function GeoPoint(data) {
@ -119,14 +123,23 @@ function GeoPoint(data) {
return new GeoPoint(data);
}
if(arguments.length === 2) {
data = {
lat: arguments[0],
lng: arguments[1]
};
}
assert(Array.isArray(data) || typeof data === 'object' || typeof data === 'string', 'must provide valid geo-coordinates array [lat, lng] or object or a "lat, lng" string');
if (typeof data === 'string') {
data = data.split(/,\s*/);
assert(data.length === 2, 'must provide a string "lng,lat" creating a GeoPoint with a string');
assert(data.length === 2, 'must provide a string "lat,lng" creating a GeoPoint with a string');
}
if (Array.isArray(data)) {
data = {
lng: Number(data[0]),
lat: Number(data[1])
lat: Number(data[0]),
lng: Number(data[1])
};
} else {
data.lng = Number(data.lng);
@ -177,7 +190,7 @@ GeoPoint.distanceBetween = function distanceBetween(a, b, options) {
var y2 = b.lng;
return geoDistance(x1, y1, x2, y2, options);
}
};
/**
* Determine the spherical distance to the given point.
@ -202,26 +215,22 @@ GeoPoint.distanceBetween = function distanceBetween(a, b, options) {
GeoPoint.prototype.distanceTo = function (point, options) {
return GeoPoint.distanceBetween(this, point, options);
}
};
/**
* Simple serialization.
*/
GeoPoint.prototype.toString = function () {
return this.lng + ',' + this.lat;
}
return this.lat + ',' + this.lng;
};
/**
* @property {Number} PI - Ratio of a circle's circumference to its diameter.
* @property {Number} DEG2RAD - Factor to convert degrees to radians.
* @property {Number} RAD2DEG - Factor to convert radians to degrees.
* @property {Object} EARTH_RADIUS - Radius of the earth.
*/
// ratio of a circle's circumference to its diameter
var PI = 3.1415926535897932384626433832795;
// factor to convert degrees to radians
var DEG2RAD = 0.01745329252;
@ -239,18 +248,23 @@ var EARTH_RADIUS = {
};
function geoDistance(x1, y1, x2, y2, options) {
var type = (options && options.type) || 'miles';
// Convert to radians
x1 = x1 * DEG2RAD;
y1 = y1 * DEG2RAD;
x2 = x2 * DEG2RAD;
y2 = y2 * DEG2RAD;
var a = Math.pow(Math.sin(( y2 - y1 ) / 2.0), 2);
var b = Math.pow(Math.sin(( x2 - x1 ) / 2.0), 2);
var c = Math.sqrt(a + Math.cos(y2) * Math.cos(y1) * b);
// use the haversine formula to calculate distance for any 2 points on a sphere.
// ref http://en.wikipedia.org/wiki/Haversine_formula
var haversine = function(a) {
return Math.pow(Math.sin(a / 2.0), 2);
};
var type = (options && options.type) || 'miles';
var f = Math.sqrt(haversine(x2 - x1) + Math.cos(x2) * Math.cos(x1) * haversine(y2 - y1));
return 2 * Math.asin(c) * EARTH_RADIUS[type];
return 2 * Math.asin(f) * EARTH_RADIUS[type];
}

View File

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

View File

@ -1122,12 +1122,14 @@ function testOrm(dataSource) {
it('should find or create', function (test) {
var email = 'some email ' + Math.random();
User.findOrCreate({where: {email: email}}, function (err, u) {
User.findOrCreate({where: {email: email}}, function (err, u, created) {
test.ok(u);
test.ok(!u.age);
User.findOrCreate({where: {email: email}}, {age: 21}, function (err, u2) {
test.ok(created);
User.findOrCreate({where: {email: email}}, {age: 21}, function (err, u2, created) {
test.equals(u.id.toString(), u2.id.toString(), 'Same user ids');
test.ok(!u2.age);
test.ok(!created);
test.done();
});
});

View File

@ -2,7 +2,7 @@
var should = require('./init.js');
var async = require('async');
var db, Category, Product, Tool, Widget, Thing;
var db, Category, Product, Tool, Widget, Thing, Person;
// This test requires a connector that can
// handle a custom collection or table name
@ -87,6 +87,10 @@ describe('default scope', function () {
memory: { collection: 'Product' }
});
Person = db.define('Person', { name: String }, {
scope: { include: 'things' }
});
// inst is only valid for instance methods
// like save, updateAttributes
@ -116,6 +120,9 @@ describe('default scope', function () {
Widget.belongsTo(Category);
Thing.belongsTo(Category);
Person.hasMany(Thing);
Thing.belongsTo(Person);
db.automigrate(done);
});
@ -805,4 +812,30 @@ describe('default scope', function () {
});
describe('with include option', function() {
before(function (done) {
db.automigrate(done);
});
before(function (done) {
Person.create({ id: 1, name: 'Person A' }, function(err, person) {
person.things.create({ name: 'Thing A' }, done);
});
});
it('should find a scoped instance with included relation - things', function(done) {
Person.findById(1, function(err, person) {
should.not.exist(err);
should.exist(person);
var things = person.things();
should.exist(things);
things.should.be.an.instanceOf(Array);
things.should.have.length(1);
done();
});
});
});
});

139
test/geo.test.js Normal file
View File

@ -0,0 +1,139 @@
/*global describe,it*/
/*jshint expr:true */
require('should');
var GeoPoint = require('../lib/geo').GeoPoint;
var DELTA = 0.0000001;
describe('GeoPoint', function () {
describe('constructor', function() {
it('should support a valid array', function () {
var point = new GeoPoint([-34, 150]);
point.lat.should.equal(-34);
point.lng.should.equal(150);
});
it('should support a valid object', function () {
var point = new GeoPoint({ lat: -34, lng: 150 });
point.lat.should.equal(-34);
point.lng.should.equal(150);
});
it('should support valid string geo coordinates', function () {
var point = new GeoPoint('-34,150');
point.lat.should.equal(-34);
point.lng.should.equal(150);
});
it('should support coordinates as inline parameters', function () {
var point = new GeoPoint(-34, 150);
point.lat.should.equal(-34);
point.lng.should.equal(150);
});
it('should reject invalid parameters', function () {
/*jshint -W024 */
var fn = function() {
new GeoPoint('150,-34');
};
fn.should.throw();
fn = function() {
new GeoPoint('invalid_string');
};
fn.should.throw();
fn = function() {
new GeoPoint([150, -34]);
};
fn.should.throw();
fn = function() {
new GeoPoint({
lat: 150,
lng: null
});
};
fn.should.throw();
fn = function() {
new GeoPoint(150, -34);
};
fn.should.throw();
fn = function() {
new GeoPoint();
};
fn.should.throw();
});
});
describe('toString()', function() {
it('should return a string in the form "lat,lng"', function() {
var point = new GeoPoint({ lat: -34, lng: 150 });
point.toString().should.equal('-34,150');
});
});
describe('distance calculation between two points', function () {
var here = new GeoPoint({ lat: 40.77492964101182, lng: -73.90950187151662 });
var there = new GeoPoint({ lat: 40.7753227, lng: -73.909217 });
it('should return value in miles by default', function () {
var distance = GeoPoint.distanceBetween(here, there);
distance.should.be.a.Number;
distance.should.be.approximately(0.03097916611592679, DELTA);
});
it('should return value using specified unit', function () {
/* Supported units:
* - `radians`
* - `kilometers`
* - `meters`
* - `miles`
* - `feet`
* - `degrees`
*/
var distance = here.distanceTo(there, { type: 'radians'});
distance.should.be.a.Number;
distance.should.be.approximately(0.000007825491914348416, DELTA);
distance = here.distanceTo(there, { type: 'kilometers'});
distance.should.be.a.Number;
distance.should.be.approximately(0.04985613511367009, DELTA);
distance = here.distanceTo(there, { type: 'meters'});
distance.should.be.a.Number;
distance.should.be.approximately(49.856135113670085, DELTA);
distance = here.distanceTo(there, { type: 'miles'});
distance.should.be.a.Number;
distance.should.be.approximately(0.03097916611592679, DELTA);
distance = here.distanceTo(there, { type: 'feet'});
distance.should.be.a.Number;
distance.should.be.approximately(163.56999709209347, DELTA);
distance = here.distanceTo(there, { type: 'degrees'});
distance.should.be.a.Number;
distance.should.be.approximately(0.0004483676593058972, DELTA);
});
});
});

View File

@ -183,7 +183,7 @@ describe('Memory connector', function () {
});
});
it('support order with multiple fields', function (done) {
it('should support order with multiple fields', function (done) {
User.find({order: 'vip ASC, seq DESC'}, function (err, posts) {
should.not.exist(err);
posts[0].seq.should.be.eql(4);
@ -192,6 +192,17 @@ describe('Memory connector', function () {
});
});
it('should sort undefined values to the end when ordered DESC', function (done) {
User.find({order: 'vip ASC, order DESC'}, function (err, posts) {
console.log(posts);
should.not.exist(err);
posts[4].seq.should.be.eql(1);
posts[5].seq.should.be.eql(0);
done();
});
});
it('should throw if order has wrong direction', function (done) {
User.find({order: 'seq ABC'}, function (err, posts) {
should.exist(err);
@ -200,11 +211,11 @@ describe('Memory connector', function () {
});
it('should support neq operator for number', function (done) {
User.find({where: {order: {neq: 6}}}, function (err, users) {
User.find({where: {seq: {neq: 4}}}, function (err, users) {
should.not.exist(err);
users.length.should.be.equal(5);
for (var i = 0; i < users.length; i++) {
users[i].order.should.not.be.equal(6);
users[i].seq.should.not.be.equal(4);
}
done();
});
@ -242,7 +253,6 @@ describe('Memory connector', function () {
email: 'john@b3atl3s.co.uk',
role: 'lead',
birthday: new Date('1980-12-08'),
order: 2,
vip: true
},
{