From f52e287c82e6b04a48c4dc6696b78fe9e14f1e1f Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Thu, 15 May 2014 08:56:00 -0700 Subject: [PATCH 01/10] Add support for logical operator (AND/OR) --- lib/connectors/memory.js | 34 ++++++++++++++++++++++------ test/basic-querying.test.js | 44 +++++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 7 deletions(-) diff --git a/lib/connectors/memory.js b/lib/connectors/memory.js index da4bc2c4..ce466221 100644 --- a/lib/connectors/memory.js +++ b/lib/connectors/memory.js @@ -332,14 +332,31 @@ Memory.prototype.all = function all(model, filter, callback) { }; function applyFilter(filter) { - if (typeof filter.where === 'function') { - return filter.where; + var where = filter.where; + if (typeof where === 'function') { + return where; } - var keys = Object.keys(filter.where); + var keys = Object.keys(where); return function (obj) { var pass = true; keys.forEach(function (key) { - if (!test(filter.where[key], obj && obj[key])) { + if(key === 'and' || key === 'or') { + if(Array.isArray(where[key])) { + if(key === 'and') { + pass = where[key].every(function(cond) { + return applyFilter({where: cond})(obj); + }); + return pass; + } + if(key === 'or') { + pass = where[key].some(function(cond) { + return applyFilter({where: cond})(obj); + }); + return pass; + } + } + } + if (!test(where[key], obj && obj[key])) { pass = false; } }); @@ -350,11 +367,14 @@ function applyFilter(filter) { if (typeof value === 'string' && example && example.constructor.name === 'RegExp') { return value.match(example); } - if (typeof example === 'undefined') return undefined; - if (typeof value === 'undefined') return undefined; + if (example === undefined || value === undefined) { + return undefined; + } if (typeof example === 'object') { // ignore geo near filter - if (example.near) return true; + if (example.near) { + return true; + } if (example.inq) { if (!value) return false; diff --git a/test/basic-querying.test.js b/test/basic-querying.test.js index 9b90d66d..ae72ca45 100644 --- a/test/basic-querying.test.js +++ b/test/basic-querying.test.js @@ -131,6 +131,50 @@ describe('basic-querying', function () { }); }); + it('should support "and" operator that is satisfied', function (done) { + User.find({where: {and: [ + {name: 'John Lennon'}, + {role: 'lead'} + ]}}, function (err, users) { + should.not.exist(err); + users.should.have.property('length', 1); + done(); + }); + }); + + it('should support "and" operator that is not satisfied', function (done) { + User.find({where: {and: [ + {name: 'John Lennon'}, + {role: 'member'} + ]}}, function (err, users) { + should.not.exist(err); + users.should.have.property('length', 0); + done(); + }); + }); + + it('should support "or" that is satisfied', function (done) { + User.find({where: {or: [ + {name: 'John Lennon'}, + {role: 'lead'} + ]}}, function (err, users) { + should.not.exist(err); + users.should.have.property('length', 2); + done(); + }); + }); + + it('should support "or" operator that is not satisfied', function (done) { + User.find({where: {or: [ + {name: 'XYZ'}, + {role: 'Hello1'} + ]}}, function (err, users) { + should.not.exist(err); + users.should.have.property('length', 0); + done(); + }); + }); + it('should only include fields as specified', function (done) { var remaining = 0; From b43cae06679fb0b72c250c86db08c636dd91e5d1 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Fri, 16 May 2014 08:50:58 -0700 Subject: [PATCH 02/10] Add a path to show customer.orders(query, cb) --- examples/relations.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/examples/relations.js b/examples/relations.js index ed3474cc..702a7071 100644 --- a/examples/relations.js +++ b/examples/relations.js @@ -3,7 +3,8 @@ var ds = new DataSource('memory'); var Order = ds.createModel('Order', { items: [String], - orderDate: Date + orderDate: Date, + qty: Number }); var Customer = ds.createModel('Customer', { @@ -43,14 +44,17 @@ Customer.create({name: 'John'}, function (err, customer) { Customer.hasMany(Order, {as: 'orders', foreignKey: 'customerId'}); Customer.create({name: 'Ray'}, function (err, customer) { - Order.create({customerId: customer.id, orderDate: new Date()}, function (err, order) { + Order.create({customerId: customer.id, qty: 3, orderDate: new Date()}, function (err, order) { order3 = order; customer.orders(console.log); - customer.orders.create({orderDate: new Date()}, function (err, order) { + customer.orders.create({orderDate: new Date(), qty: 4}, function (err, order) { console.log(order); Customer.include([customer], 'orders', function (err, results) { console.log('Results: ', results); }); + customer.orders({where: {qty: 4}}, function(err, results) { + console.log('customer.orders', results); + }); customer.orders.findById(order3.id, console.log); customer.orders.destroy(order3.id, console.log); }); From 4abb4d2fdf8f5b74910769ecdb220064b0f2c3ec Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Fri, 16 May 2014 08:52:25 -0700 Subject: [PATCH 03/10] Bump version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a569f21f..09efe955 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "loopback-datasource-juggler", - "version": "1.5.0", + "version": "1.5.1", "description": "LoopBack DataSoure Juggler", "keywords": [ "StrongLoop", From 438df25d8e32f1c424213ec9db895914c0211af0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20Bajto=C5=A1?= Date: Tue, 20 May 2014 10:47:44 +0200 Subject: [PATCH 04/10] validations: include more details in `err.message` Modify ValidationError constructor to include the model name and a human-readable representation of the validation errors (messages) in the error message. Before this change, the message was pointing the reader to `err.details`. Most frameworks (e.g. express, mocha) log only `err.message` but not other error properties, thus the logs were rather unhelpful. Example of the new error message: The `User` instance is not valid. Details: `name` can't be blank. --- lib/validations.js | 28 +++++++++++++++++++++++++--- test/validations.test.js | 19 +++++++++++++++++++ 2 files changed, 44 insertions(+), 3 deletions(-) diff --git a/lib/validations.js b/lib/validations.js index 200bf6ba..a12a8127 100644 --- a/lib/validations.js +++ b/lib/validations.js @@ -687,12 +687,18 @@ function ValidationError(obj) { if (!(this instanceof ValidationError)) return new ValidationError(obj); this.name = 'ValidationError'; - this.message = 'The Model instance is not valid. ' + - 'See `details` property of the error object for more info.'; + + var context = obj && obj.constructor && obj.constructor.modelName; + this.message = util.format( + 'The %s instance is not valid. Details: %s.', + context ? '`' + context + '`' : 'model', + formatErrors(obj.errors) || '(unknown)' + ); + this.statusCode = 422; this.details = { - context: obj && obj.constructor && obj.constructor.modelName, + context: context, codes: obj.errors && obj.errors.codes, messages: obj.errors }; @@ -701,3 +707,19 @@ function ValidationError(obj) { } util.inherits(ValidationError, Error); + +function formatErrors(errors) { + var DELIM = '; '; + errors = errors || {}; + return Object.getOwnPropertyNames(errors) + .filter(function(propertyName) { + return Array.isArray(errors[propertyName]); + }) + .map(function(propertyName) { + var messages = errors[propertyName]; + return messages.map(function(msg) { + return '`' + propertyName + '` ' + msg; + }).join(DELIM); + }) + .join(DELIM); +} diff --git a/test/validations.test.js b/test/validations.test.js index f01414aa..c3b5c8d4 100644 --- a/test/validations.test.js +++ b/test/validations.test.js @@ -111,6 +111,25 @@ describe('validations', function () { done(); }); + it('should include validation messages in err.message', function(done) { + delete User._validations; + User.validatesPresenceOf('name'); + User.create(function (e, u) { + should.exist(e); + e.message.should.match(/`name` can't be blank/); + done(); + }); + }); + + it('should include model name in err.message', function(done) { + delete User._validations; + User.validatesPresenceOf('name'); + User.create(function (e, u) { + should.exist(e); + e.message.should.match(/`User` instance/i); + done(); + }); + }); }); }); From dc3d2233e735fc23428c7db54a28bfe84253c223 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20Bajto=C5=A1?= Date: Tue, 20 May 2014 17:59:05 +0200 Subject: [PATCH 05/10] 1.5.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 09efe955..fb56d97e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "loopback-datasource-juggler", - "version": "1.5.1", + "version": "1.5.2", "description": "LoopBack DataSoure Juggler", "keywords": [ "StrongLoop", From ea263f86eee531fde1bd93d808f59aa7f1347a1f Mon Sep 17 00:00:00 2001 From: crandmck Date: Wed, 21 May 2014 17:50:44 -0700 Subject: [PATCH 06/10] Copy info from api-model.md to JSDoc --- lib/dao.js | 138 ++++++++++++++++++++++++++++++++++++----------- lib/relations.js | 44 ++++++++++++--- 2 files changed, 143 insertions(+), 39 deletions(-) diff --git a/lib/dao.js b/lib/dao.js index aad2cc6c..99c2fc4a 100644 --- a/lib/dao.js +++ b/lib/dao.js @@ -22,12 +22,10 @@ var removeUndefined = utils.removeUndefined; /** * Base class for all persistent objects. * Provides a common API to access any database connector. - * This class describes only abstract behavior. Refer to the specific connector (`lib/connectors/*.js`) for details. + * This class describes only abstract behavior. Refer to the specific connector for additional details. * * `DataAccessObject` mixes `Inclusion` classes methods. - * * @class DataAccessObject - * @param {Object} data Initial object data */ function DataAccessObject() { if (DataAccessObject._mixins) { @@ -39,6 +37,8 @@ function DataAccessObject() { } } + + function idName(m) { return m.getDataSource().idName ? m.getDataSource().idName(m.modelName) : 'id'; @@ -71,15 +71,20 @@ DataAccessObject._forDB = function (data) { }; /** - * Create new instance of Model class, saved in database. - * The callback function is called with arguments: + * Create an instance of Model with given data and save to the attached data source. Callback is optional. + * Example: + *```js + * User.create({first: 'Joe', last: 'Bob'}, function(err, user) { + * console.log(user instanceof User); // true + * }); + * ``` + * Note: You must include a callback and use the created model provided in the callback if your code depends on your model being + * saved or having an ID. * + * @param {Object} data Optional data object + * @param {Function} callback Callback function called with these arguments: * - err (null or Error) * - instance (null or Model) - * - * @param data {Object} Optional data object - * @param callback {Function} Callback function - */ DataAccessObject.create = function (data, callback) { if (stillConnecting(this.getDataSource(), this, arguments)) return; @@ -210,7 +215,10 @@ function stillConnecting(dataSource, obj, args) { } /** - * Update or insert a model instance. + * Update or insert a model instance: update exiting record if one is found, such that parameter `data.id` matches `id` of model instance; + * otherwise, insert a new record. + * + * NOTE: No setters, validations, or hooks are applied when using upsert. * `updateOrCreate` is an alias * @param {Object} data The model instance data * @param {Function} callback The callback function (optional). @@ -269,10 +277,12 @@ setRemoting(DataAccessObject.upsert, { }); /** - * Find one record, same as `all`, limited by 1 and return object, not collection, - * if not found, create using data provided as second argument + * Find one record that matches specified query criteria. Same as `find`, but limited to one record, and this function returns an + * object, not a collection. + * If the specified instance is not found, then create it using data provided as second argument. * - * @param {Object} query Search conditions: {where: {test: 'me'}}. + * @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) */ @@ -323,7 +333,14 @@ setRemoting(DataAccessObject.exists, { }); /** - * Find object by id + * Find model instance by ID. + * + * Example: + * ```js + * User.findById(23, function(err, user) { + * console.info(user.id); // 23 + * }); + * ``` * * @param {*} id Primary key value * @param {Function} cb Callback called with (err, instance) @@ -477,18 +494,59 @@ DataAccessObject._coerce = function (where) { }; /** - * Find all instances of Model, matched by query - * make sure you have marked as `index: true` fields for filter or sort - * - * @param {Object} [query] the query object - * - * - where: Object `{ key: val, key2: {gt: 'val2'}}` - * - include: String, Object or Array. See `DataAccessObject.include()`. - * - order: String - * - limit: Number - * - skip: Number + * Find all instances of Model that match the specified query. + * Fields used for filter and sort should be declared with `{index: true}` in model definition. + * See [Querying models](http://docs.strongloop.com/display/DOC/Querying+models) for more information. * - * @param {Function} callback (required) called with two arguments: err (null or Error), array of instances + * For example, find the second page of ten users over age 21 in descending order exluding the password property. + * + * ```js + * User.find({ + * where: { + * age: {gt: 21}}, + * order: 'age DESC', + * limit: 10, + * skip: 10, + * fields: {password: false} + * }, + * console.log + * ); + * ``` + * + * @options {Object} [query] Optional JSON object that specifies query criteria and parameters. + * @property {Object} where Search criteria in JSON format `{ key: val, key2: {gt: 'val2'}}`. + * Operations: + * - gt: > + * - gte: >= + * - lt: < + * - lte: <= + * - between + * - inq: IN + * - nin: NOT IN + * - neq: != + * - like: LIKE + * - nlike: NOT LIKE + * + * You can also use `and` and `or` operations. See [Querying models](http://docs.strongloop.com/display/DOC/Querying+models) for more information. + * @property {String|Object|Array} include Allows you to load relations of several objects and optimize numbers of requests. + * Format examples; + * - `'posts'`: Load posts + * - `['posts', 'passports']`: Load posts and passports + * - `{'owner': 'posts'}`: Load owner and owner's posts + * - `{'owner': ['posts', 'passports']}`: Load owner, owner's posts, and owner's passports + * - `{'owner': [{posts: 'images'}, 'passports']}`: Load owner, owner's posts, owner's posts' images, and owner's passports + * See `DataAccessObject.include()`. + * @property {String} order Sort order. Format: `'key1 ASC, key2 DESC'` + * @property {Number} limit Maximum number of instances to return. + * @property {Number} skip Number of instances to skip. + * @property {Number} offset Alias for `skip`. + * @property {Object|Array|String} fields Included/excluded fields. + * - `['foo']` or `'foo'` - include only the foo property + * - `['foo', 'bar']` - include the foo and bar properties. Format: + * - `{foo: true}` - include only foo + * - `{bat: false}` - include all properties, exclude bat + * + * @param {Function} callback Required callback function. Call this function with two arguments: `err` (null or Error) and an array of instances. */ DataAccessObject.find = function find(query, cb) { @@ -613,10 +671,11 @@ setRemoting(DataAccessObject.find, { }); /** - * Find one record, same as `all`, limited by 1 and return object, not collection + * Find one record, same as `find`, but limited to one result. This function returns an object, not a collection. * - * @param {Object} query - search conditions: {where: {test: 'me'}} - * @param {Function} cb - callback called with (err, instance) + * @param {Object} query Sarch conditions. See [find](#dataaccessobjectfindquery-callback) for query format. + * For example: `{where: {test: 'me'}}`. + * @param {Function} cb Callback function called with (err, instance) */ DataAccessObject.findOne = function findOne(query, cb) { if (stillConnecting(this.getDataSource(), this, arguments)) return; @@ -641,8 +700,16 @@ setRemoting(DataAccessObject.findOne, { }); /** - * Destroy all matching records - * @param {Object} [where] An object that defines the criteria + * Destroy all matching records. + * Delete all model instances from data source. Note: destroyAll method does not destroy hooks. + * Example: + *````js + * Product.destroyAll({price: {gt: 99}}, function(err) { + // removed matching products + * }); + * ```` + * + * @param {Object} [where] Optional object that defines the criteria. This is a "where" object. Do NOT pass a filter object. * @param {Function} [cb] Callback called with (err) */ DataAccessObject.remove = DataAccessObject.deleteAll = DataAccessObject.destroyAll = function destroyAll(where, cb) { @@ -697,9 +764,16 @@ setRemoting(DataAccessObject.deleteById, { }); /** - * Return count of matched records + * Return count of matched records. Optional query parameter allows you to count filtered set of model instances. + * Example: + * + *```js + * User.count({approved: true}, function(err, count) { + * console.log(count); // 2081 + * }); + * ``` * - * @param {Object} where Search conditions (optional) + * @param {Object} [where] Search conditions (optional) * @param {Function} cb Callback, called with (err, count) */ DataAccessObject.count = function (where, cb) { diff --git a/lib/relations.js b/lib/relations.js index a18da4e9..81495170 100644 --- a/lib/relations.js +++ b/lib/relations.js @@ -61,8 +61,37 @@ function lookupModel(models, modelName) { * ``` * Book.hasMany('chapters', {model: Chapter}); * ``` - * @param {Relation} anotherClass Class to has many - * @options {Object} parameters Configuration parameters + * + * Query and create related models: + * + * ```js + * Book.create(function(err, book) { + * + * // Create a chapter instance ready to be saved in the data source. + * var chapter = book.chapters.build({name: 'Chapter 1'}); + * + * // Save the new chapter + * chapter.save(); + * + * // you can also call the Chapter.create method with the `chapters` property which will build a chapter + * // instance and save the it in the data source. + * book.chapters.create({name: 'Chapter 2'}, function(err, savedChapter) { + * // this callback is optional + * }); + * + * // Query chapters for the book + * book.chapters(function(err, chapters) { // all chapters with bookId = book.id + * console.log(chapters); + * }); + * + * book.chapters({where: {name: 'test'}, function(err, chapters) { + * // All chapters with bookId = book.id and name = 'test' + * console.log(chapters); + * }); + * }); + *``` + * @param {Object|String} anotherClass Model object (or String name of model) to which you are creating the relationship. + * @options {Object} parameters Configuration parameters; see below. * @property {String} as * @property {String} foreignKey Property name of foreign key field. * @property {Object} model Model object @@ -275,8 +304,8 @@ Relation.hasMany = function hasMany(anotherClass, params) { * ``` * This optional parameter default value is false, so the related object will be loaded from cache if available. * - * @param {Class} anotherClass Class to belong - * @param {Object} Parameters Configuration parameters + * @param {Class|String} anotherClass Model object (or String name of model) to which you are creating the relationship. + * @options {Object} params Configuration parameters; see below. * @property {String} as Can be 'propertyName' * @property {String} foreignKey Name of foreign key property. * @@ -446,11 +475,12 @@ Relation.belongsTo = function (anotherClass, params) { * user.groups.remove(group, callback); * ``` * - * @param {String|Function} anotherClass - target class to hasAndBelongsToMany or name of + * @param {String|Object} anotherClass Model object (or String name of model) to which you are creating the relationship. * the relation - * @options {Object} params - configuration {as: String, foreignKey: *, model: ModelClass} - * @property {Object} model Model name + * @options {Object} params Configuration parameters; see below. + * @property {String} as * @property {String} foreignKey Property name of foreign key field. + * @property {Object} model Model object */ Relation.hasAndBelongsToMany = function hasAndBelongsToMany(anotherClass, params) { params = params || {}; From e2789134b031a7692c1b1cfd68c39631b60d00fd Mon Sep 17 00:00:00 2001 From: crandmck Date: Thu, 22 May 2014 15:02:57 -0700 Subject: [PATCH 07/10] Remove JSDocs for scopeMethods.add(acInst) and scopeMethods.remove(acInst) --- lib/relations.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/relations.js b/lib/relations.js index 81495170..c07b87d2 100644 --- a/lib/relations.js +++ b/lib/relations.js @@ -92,7 +92,7 @@ function lookupModel(models, modelName) { *``` * @param {Object|String} anotherClass Model object (or String name of model) to which you are creating the relationship. * @options {Object} parameters Configuration parameters; see below. - * @property {String} as + * @property {String} as Name of the property in the referring model that corresponds to the foreign key field in the related model. * @property {String} foreignKey Property name of foreign key field. * @property {Object} model Model object */ @@ -166,9 +166,9 @@ Relation.hasMany = function hasMany(anotherClass, params) { }); }; - /** + /*! * Add the target model instance to the 'hasMany' relation - * @param {Object|ID) acInst The actual instance or id value + * @param {Object|ID} acInst The actual instance or id value */ scopeMethods.add = function (acInst, done) { var data = {}; @@ -181,7 +181,7 @@ Relation.hasMany = function hasMany(anotherClass, params) { params.through.findOrCreate({where: query}, data, done); }; - /** + /*! * Remove the target model instance from the 'hasMany' relation * @param {Object|ID) acInst The actual instance or id value */ @@ -306,7 +306,7 @@ Relation.hasMany = function hasMany(anotherClass, params) { * * @param {Class|String} anotherClass Model object (or String name of model) to which you are creating the relationship. * @options {Object} params Configuration parameters; see below. - * @property {String} as Can be 'propertyName' + * @property {String} as Name of the property in the referring model that corresponds to the foreign key field in the related model. * @property {String} foreignKey Name of foreign key property. * */ @@ -478,7 +478,7 @@ Relation.belongsTo = function (anotherClass, params) { * @param {String|Object} anotherClass Model object (or String name of model) to which you are creating the relationship. * the relation * @options {Object} params Configuration parameters; see below. - * @property {String} as + * @property {String} as Name of the property in the referring model that corresponds to the foreign key field in the related model. * @property {String} foreignKey Property name of foreign key field. * @property {Object} model Model object */ From 7a087f358374c5c1ea739b45db08ebaf0cdd4367 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Mon, 26 May 2014 08:21:23 -0700 Subject: [PATCH 08/10] Keep undefined/null values for the array type This allows connectors to distinguish between empty array and undefined/null. For example, mongodb will not override existing array properties if the value is undefined. --- lib/model.js | 11 +++++++---- test/loopback-dl.test.js | 9 ++++++--- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/lib/model.js b/lib/model.js index b38b7120..508c5b6d 100644 --- a/lib/model.js +++ b/lib/model.js @@ -134,8 +134,9 @@ ModelBaseClass.prototype._initProperties = function (data, options) { } } + var propertyName; if (applySetters === true) { - for (var propertyName in data) { + for (propertyName in data) { if (typeof data[propertyName] !== 'function' && ((propertyName in properties) || (propertyName in ctor.relations))) { self[propertyName] = self.__data[propertyName] || data[propertyName]; } @@ -144,7 +145,7 @@ ModelBaseClass.prototype._initProperties = function (data, options) { // Set the unknown properties as properties to the object if (strict === false) { - for (var propertyName in data) { + for (propertyName in data) { if (typeof data[propertyName] !== 'function' && !(propertyName in properties)) { self[propertyName] = self.__data[propertyName] || data[propertyName]; } @@ -174,8 +175,10 @@ ModelBaseClass.prototype._initProperties = function (data, options) { } } if (type.name === 'Array' || Array.isArray(type)) { - if (!(self.__data[propertyName] instanceof List)) { - self.__data[propertyName] = new List(self.__data[propertyName], type, self); + if (!(self.__data[propertyName] instanceof List) + && self.__data[propertyName] !== undefined + && self.__data[propertyName] !== null ) { + self.__data[propertyName] = List(self.__data[propertyName], type, self); } } } diff --git a/test/loopback-dl.test.js b/test/loopback-dl.test.js index 6a1e013d..7f247007 100644 --- a/test/loopback-dl.test.js +++ b/test/loopback-dl.test.js @@ -582,7 +582,8 @@ describe('Models attached to a dataSource', function() { var ds = new DataSource('memory');// define models Post = ds.define('Post', { title: { type: String, length: 255, index: true }, - content: { type: String } + content: { type: String }, + comments: [String] }); }); @@ -613,9 +614,10 @@ describe('Models attached to a dataSource', function() { }); it('updateOrCreate should update the instance without removing existing properties', function (done) { - Post.create({title: 'a', content: 'AAA'}, function (err, post) { + Post.create({title: 'a', content: 'AAA', comments: ['Comment1']}, function (err, post) { post = post.toObject(); delete post.title; + delete post.comments; Post.updateOrCreate(post, function (err, p) { should.not.exist(err); p.id.should.be.equal(post.id); @@ -627,7 +629,8 @@ describe('Models attached to a dataSource', function() { should.not.exist(p._id); p.content.should.be.equal(post.content); p.title.should.be.equal('a'); - + p.comments.length.should.be.equal(1); + p.comments[0].should.be.equal('Comment1'); done(); }); }); From 6662bde84d0ef0361989ec12e2ee1357b84b98b9 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Tue, 27 May 2014 16:11:57 -0700 Subject: [PATCH 09/10] Bump version --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index fb56d97e..70607672 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "loopback-datasource-juggler", - "version": "1.5.2", + "version": "1.5.3", "description": "LoopBack DataSoure Juggler", "keywords": [ "StrongLoop", @@ -27,7 +27,7 @@ "mocha": "~1.18.2" }, "dependencies": { - "async": "~0.8.0", + "async": "~0.9.0", "inflection": "~1.3.5", "traverse": "~0.6.6", "qs": "~0.6.6", From 01410c3495715e84103abc36ff1628f85a35c636 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Tue, 27 May 2014 22:05:42 -0700 Subject: [PATCH 10/10] Bump version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 70607672..24c3fc7d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "loopback-datasource-juggler", - "version": "1.5.3", + "version": "1.5.4", "description": "LoopBack DataSoure Juggler", "keywords": [ "StrongLoop",