From 97c5cfd6443c9e38e74adcfca9f5f229000cfa33 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Fri, 28 Feb 2014 22:29:22 -0800 Subject: [PATCH 1/7] Fix the example for scope --- examples/datasource-app.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/datasource-app.js b/examples/datasource-app.js index 55f40cd0..215ad986 100644 --- a/examples/datasource-app.js +++ b/examples/datasource-app.js @@ -51,7 +51,7 @@ Post.belongsTo(User, {as: 'author', foreignKey: 'userId'}); User.hasAndBelongsToMany('groups'); -var user2 = new User({name: 'Smith'}); +var user2 = new User({name: 'Smith', age: 14}); user2.save(function (err) { console.log(user2); var post = user2.posts.build({title: 'Hello world'}); @@ -64,7 +64,7 @@ Post.findOne({where: {published: false}, order: 'date DESC'}, function (err, dat console.log(data); }); -User.create({name: 'Jeff'}, function (err, data) { +User.create({name: 'Jeff', age: 12}, function (err, data) { if (err) { console.log(err); return; @@ -78,7 +78,7 @@ User.create({name: 'Ray'}, function (err, data) { console.log(data); }); -User.scope('minors', {age: {le: 16}}); +User.scope('minors', {where: {age: {lte: 16}}, include: 'posts'}); User.minors(function (err, kids) { console.log('Kids: ', kids); }); From 14745b13051d7a1788cb156f1466f69b18f90ee9 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Mon, 3 Mar 2014 15:03:05 -0800 Subject: [PATCH 2/7] Make the belongsTo relation remotable --- lib/relations.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lib/relations.js b/lib/relations.js index a5372f92..5b799556 100644 --- a/lib/relations.js +++ b/lib/relations.js @@ -277,6 +277,15 @@ Relation.belongsTo = function (anotherClass, params) { } }; + // Set the remoting metadata so that it can be accessed as /api/// + // For example, /api/orders/1/customer + var fn = this.prototype[methodName]; + fn.shared = true; + fn.http = {verb: 'get', path: '/' + methodName}; + fn.accepts = {arg: 'refresh', type: 'boolean', http: {source: 'query'}}; + fn.description = 'Fetches belongsTo relation ' + methodName; + fn.returns = {arg: methodName, type: 'object', root: true}; + }; /** From 510f5ef6aca71f062c02b2b13b3dd69c6d0114c2 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Mon, 3 Mar 2014 15:52:49 -0800 Subject: [PATCH 3/7] Check the Array type See https://github.com/strongloop/loopback-connector-mongodb/issues/21 --- lib/list.js | 5 +++++ test/datatype.test.js | 10 ++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/lib/list.js b/lib/list.js index d679d570..2a2da03a 100644 --- a/lib/list.js +++ b/lib/list.js @@ -1,4 +1,5 @@ var util = require('util'); +var Any = require('./types').Types.Any; module.exports = List; @@ -32,6 +33,10 @@ function List(items, itemType, parent) { itemType = itemType[0]; } + if(itemType === Array) { + itemType = Any; + } + Object.defineProperty(arr, 'itemType', { writable: true, enumerable: false, diff --git a/test/datatype.test.js b/test/datatype.test.js index 4db0d506..13f83a20 100644 --- a/test/datatype.test.js +++ b/test/datatype.test.js @@ -13,6 +13,7 @@ describe('datatypes', function () { num: Number, bool: Boolean, list: {type: [String]}, + arr: Array }); db.automigrate(function () { Model.destroyAll(done); @@ -23,25 +24,30 @@ describe('datatypes', function () { var d = new Date, id; Model.create({ - str: 'hello', date: d, num: '3', bool: 1, list: ['test'] + str: 'hello', date: d, num: '3', bool: 1, list: ['test'], arr: [1, 'str'] }, function (err, m) { should.not.exist(err); should.exist(m && m.id); m.str.should.be.a('string'); m.num.should.be.a('number'); m.bool.should.be.a('boolean'); + m.list[0].should.be.equal('test'); + m.arr[0].should.be.equal(1); + m.arr[1].should.be.equal('str'); id = m.id; testFind(testAll); }); function testFind(next) { - debugger; Model.findById(id, function (err, m) { should.not.exist(err); should.exist(m); m.str.should.be.a('string'); m.num.should.be.a('number'); m.bool.should.be.a('boolean'); + m.list[0].should.be.equal('test'); + m.arr[0].should.be.equal(1); + m.arr[1].should.be.equal('str'); m.date.should.be.an.instanceOf(Date); m.date.toString().should.equal(d.toString(), 'Time must match'); next(); From 24c4381221e8a333629516d64729378fad5bffc1 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Mon, 3 Mar 2014 17:16:37 -0800 Subject: [PATCH 4/7] Allows scopes to be defined in LDL --- lib/datasource.js | 18 ++++++++++++++++++ lib/scope.js | 12 ++++++++++++ test/loopback-dl.test.js | 28 ++++++++++++++++++++++++++++ 3 files changed, 58 insertions(+) diff --git a/lib/datasource.js b/lib/datasource.js index 0bff0eee..99dd6565 100644 --- a/lib/datasource.js +++ b/lib/datasource.js @@ -7,6 +7,7 @@ var jutil = require('./jutil'); var utils = require('./utils'); var ModelBaseClass = require('./model.js'); var DataAccessObject = require('./dao.js'); +var defineScope = require('./scope.js').defineScope; var EventEmitter = require('events').EventEmitter; var util = require('util'); var assert = require('assert'); @@ -346,6 +347,20 @@ DataSource.relationTypes = ['belongsTo', 'hasMany', 'hasAndBelongsToMany']; function isModelDataSourceAttached(model) { return model && (!model.settings.unresolved) && (model.dataSource instanceof DataSource); } + +/*! + * Define scopes for the model class from the scopes object + * @param modelClass + * @param scopes + */ +DataSource.prototype.defineScopes = function (modelClass, scopes) { + if(scopes) { + for(var s in scopes) { + defineScope(modelClass, modelClass, s, scopes[s]); + } + } +}; + /*! * Define relations for the model class from the relations object * @param modelClass @@ -451,6 +466,9 @@ DataSource.prototype.setupDataAccess = function (modelClass, settings) { var relations = settings.relationships || settings.relations; this.defineRelations(modelClass, relations); + var scopes = settings.scopes || {}; + this.defineScopes(modelClass, scopes); + }; /** diff --git a/lib/scope.js b/lib/scope.js index a54aad37..d5ec8b61 100644 --- a/lib/scope.js +++ b/lib/scope.js @@ -188,6 +188,18 @@ function defineScope(cls, targetClass, name, params, methods) { base.order = update.order; } + if(update.limit !== undefined) { + base.limit = update.limit; + } + if(update.skip !== undefined) { + base.skip = update.skip; + } + if(update.offset !== undefined) { + base.offset = update.offset; + } + if(update.fields !== undefined) { + base.fields = update.fields; + } return base; } diff --git a/test/loopback-dl.test.js b/test/loopback-dl.test.js index e826e188..4ac1f91b 100644 --- a/test/loopback-dl.test.js +++ b/test/loopback-dl.test.js @@ -1,6 +1,7 @@ // This test written in mocha+should.js var should = require('./init.js'); var assert = require('assert'); +var async = require('async'); var jdb = require('../'); var ModelBuilder = jdb.ModelBuilder; @@ -796,6 +797,33 @@ describe('Load models with relations', function () { }); +describe('Model with scopes', function () { + it('should create scopes', function (done) { + var ds = new DataSource('memory'); + var User = ds.define('User', {name: String, vip: Boolean, age: Number}, + {scopes: {vips: {where: {vip: true}}, top5: {limit: 5, order: 'age'}}}); + + var users = []; + for (var i = 0; i < 10; i++) { + users.push({name: 'User' + i, vip: i % 3 === 0, age: 20 + i * 2}); + } + async.each(users, function (user, callback) { + User.create(user, callback); + }, function (err) { + User.vips(function (err, vips) { + if(err) { + return done(err); + } + assert.equal(vips.length, 4); + User.top5(function (err, top5) { + assert.equal(top5.length, 5); + done(err); + }); + }); + }); + }); +}); + describe('DataAccessObject', function () { var ds, model, where; From ed7e2e2ada0238d0723caaa471516403168274cc Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Tue, 4 Mar 2014 08:56:16 -0800 Subject: [PATCH 5/7] Fix the parameter name --- lib/scope.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/scope.js b/lib/scope.js index d5ec8b61..5c8d43c5 100644 --- a/lib/scope.js +++ b/lib/scope.js @@ -103,7 +103,7 @@ function defineScope(cls, targetClass, name, params, methods) { fn.shared = true; fn.http = {verb: 'get', path: '/' + name}; - fn.accepts = {arg: 'where', type: 'object'}; + fn.accepts = {arg: 'filter', type: 'object'}; fn.description = 'Fetches ' + name; fn.returns = {arg: name, type: 'array', root: true}; From d14c58775d2d0970cf6bd199f59dd12814e48310 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Tue, 4 Mar 2014 09:42:55 -0800 Subject: [PATCH 6/7] Use debug module for logging --- lib/datasource.js | 53 +++++++++++++++++++++++++++-------------------- package.json | 3 ++- 2 files changed, 33 insertions(+), 23 deletions(-) diff --git a/lib/datasource.js b/lib/datasource.js index 99dd6565..42c612f6 100644 --- a/lib/datasource.js +++ b/lib/datasource.js @@ -13,6 +13,12 @@ var util = require('util'); var assert = require('assert'); var async = require('async'); +if (process.env.DEBUG === 'loopback') { + // For back-compatibility + process.env.DEBUG = 'loopback:*'; +} +var debug = require('debug')('loopback:datasource'); + /** * Export public API */ @@ -239,11 +245,10 @@ DataSource.prototype.setup = function (name, settings) { // just save everything we get this.settings = settings || {}; - // Check the debug env settings - var debugEnv = process.env.DEBUG || process.env.NODE_DEBUG || ''; - var debugModules = debugEnv.split(/[\s,]+/); - if (debugModules.indexOf('loopback') !== -1) { - this.settings.debug = true; + this.settings.debug = this.settings.debug || debug.enabled; + + if(this.settings.debug) { + debug('Settings: %j', this.settings); } // Disconnected by default @@ -673,7 +678,7 @@ DataSource.prototype.defineProperty = function (model, prop, params) { this.modelBuilder.defineProperty(model, prop, params); var resolvedProp = this.getModelDefinition(model).properties[prop]; - if (this.connector.defineProperty) { + if (this.connector && this.connector.defineProperty) { this.connector.defineProperty(model, prop, resolvedProp); } }; @@ -740,7 +745,7 @@ DataSource.prototype.discoverModelDefinitions = function (options, cb) { if (this.connector.discoverModelDefinitions) { this.connector.discoverModelDefinitions(options, cb); } else if (cb) { - cb(); + process.nextTick(cb); } }; @@ -785,7 +790,7 @@ DataSource.prototype.discoverModelProperties = function (modelName, options, cb) if (this.connector.discoverModelProperties) { this.connector.discoverModelProperties(modelName, options, cb); } else if (cb) { - cb(); + process.nextTick(cb); } }; @@ -829,7 +834,7 @@ DataSource.prototype.discoverPrimaryKeys = function (modelName, options, cb) { if (this.connector.discoverPrimaryKeys) { this.connector.discoverPrimaryKeys(modelName, options, cb); } else if (cb) { - cb(); + process.nextTick(cb); } }; @@ -876,7 +881,7 @@ DataSource.prototype.discoverForeignKeys = function (modelName, options, cb) { if (this.connector.discoverForeignKeys) { this.connector.discoverForeignKeys(modelName, options, cb); } else if (cb) { - cb(); + process.nextTick(cb); } }; @@ -893,7 +898,7 @@ DataSource.prototype.discoverForeignKeysSync = function (modelName, options) { return this.connector.discoverForeignKeysSync(modelName, options); } return null; -} +}; /** * Retrieves a description of the foreign key columns that reference the given table's primary key columns (the foreign keys exported by a table). @@ -924,7 +929,7 @@ DataSource.prototype.discoverExportedForeignKeys = function (modelName, options, if (this.connector.discoverExportedForeignKeys) { this.connector.discoverExportedForeignKeys(modelName, options, cb); } else if (cb) { - cb(); + process.nextTick(cb); } }; @@ -989,7 +994,7 @@ DataSource.prototype.discoverSchema = function (modelName, options, cb) { return; } }); -} +}; /** * Discover schema from a given modelName/view @@ -1046,7 +1051,7 @@ DataSource.prototype.discoverSchemas = function (modelName, options, cb) { }); if (self.settings.debug) { - console.log('Primary keys: ', pks); + debug('Primary keys: ', pks); } var schema = { @@ -1093,7 +1098,7 @@ DataSource.prototype.discoverSchemas = function (modelName, options, cb) { var schemaKey = columns[0].owner + '.' + modelName; if (!options.visited.hasOwnProperty(schemaKey)) { if (self.settings.debug) { - console.log('Adding schema for ' + schemaKey); + debug('Adding schema for ' + schemaKey); } options.visited[schemaKey] = schema; } @@ -1118,7 +1123,7 @@ DataSource.prototype.discoverSchemas = function (modelName, options, cb) { }); if (self.settings.debug) { - console.log('Foreign keys: ', fks); + debug('Foreign keys: ', fks); } schema.options.relations = {}; @@ -1143,7 +1148,7 @@ DataSource.prototype.discoverSchemas = function (modelName, options, cb) { var moreTasks = []; for (var t in otherTables) { if (self.settings.debug) { - console.log('Discovering related schema for ' + schemaKey); + debug('Discovering related schema for ' + schemaKey); } var newOptions = {}; for (var key in options) { @@ -1191,7 +1196,7 @@ DataSource.prototype.discoverSchemasSync = function (modelName, options) { }); if (self.settings.debug) { - console.log('Primary keys: ', pks); + debug('Primary keys: ', pks); } var schema = { @@ -1238,7 +1243,7 @@ DataSource.prototype.discoverSchemasSync = function (modelName, options) { var schemaKey = columns[0].owner + '.' + modelName; if (!options.visited.hasOwnProperty(schemaKey)) { if (self.settings.debug) { - console.log('Adding schema for ' + schemaKey); + debug('Adding schema for ' + schemaKey); } options.visited[schemaKey] = schema; } @@ -1264,7 +1269,7 @@ DataSource.prototype.discoverSchemasSync = function (modelName, options) { }); if (self.settings.debug) { - console.log('Foreign keys: ', fks); + debug('Foreign keys: ', fks); } schema.options.relations = {}; @@ -1289,7 +1294,7 @@ DataSource.prototype.discoverSchemasSync = function (modelName, options) { var moreTasks = []; for (var t in otherTables) { if (self.settings.debug) { - console.log('Discovering related schema for ' + schemaKey); + debug('Discovering related schema for ' + schemaKey); } var newOptions = {}; for (var key in options) { @@ -1395,6 +1400,7 @@ DataSource.prototype.isActual = function (models, cb) { * @private used by connectors */ DataSource.prototype.log = function (sql, t) { + debug(sql, t); this.emit('log', sql, t); }; @@ -1402,13 +1408,16 @@ DataSource.prototype.log = function (sql, t) { * Freeze dataSource. Behavior depends on connector */ DataSource.prototype.freeze = function freeze() { + if(!this.connector) { + throw new Error('The connector has not been initialized.'); + } if (this.connector.freezeDataSource) { this.connector.freezeDataSource(); } if (this.connector.freezeSchema) { this.connector.freezeSchema(); } -} +}; /** * Return table name for specified `modelName` diff --git a/package.json b/package.json index 06f35ecf..02a167cd 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,8 @@ "async": "~0.2.10", "inflection": "~1.3.5", "traverse": "~0.6.6", - "qs": "~0.6.6" + "qs": "~0.6.6", + "debug": "~0.7.4" }, "license": { "name": "Dual MIT/StrongLoop", From 7c9f48d122d475587bd739e13b3e7b785948cb13 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Tue, 4 Mar 2014 10:40:58 -0800 Subject: [PATCH 7/7] Bump version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 02a167cd..01816627 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "loopback-datasource-juggler", - "version": "1.3.5", + "version": "1.3.6", "description": "LoopBack DataSoure Juggler", "keywords": [ "StrongLoop",