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); }); 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/lib/dao.js b/lib/dao.js index bc20d505..aad2cc6c 100644 --- a/lib/dao.js +++ b/lib/dao.js @@ -359,7 +359,7 @@ function convertNullToNotFoundError(ctx, cb) { var modelName = ctx.method.sharedClass.name; var id = ctx.getArgByName('id'); - var msg = 'Unkown "' + modelName + '" id "' + id + '".'; + var msg = 'Unknown "' + modelName + '" id "' + id + '".'; var error = new Error(msg); error.statusCode = error.status = 404; cb(error); diff --git a/lib/relations.js b/lib/relations.js index dca6170a..a18da4e9 100644 --- a/lib/relations.js +++ b/lib/relations.js @@ -8,7 +8,7 @@ var ModelBaseClass = require('./model.js'); module.exports = Relation; /** - * Relations class + * Relations class. Use to define relationships between models. * * @class Relation */ @@ -47,12 +47,25 @@ function lookupModel(models, modelName) { } /** - * Declare "hasMany" relation. - * Example: - * ```User.hasMany(Post, {as: 'posts', foreignKey: 'authorId'});``` + * Define a "one to many" relationship by specifying the model name * + * Examples: + * ``` + * User.hasMany(Post, {as: 'posts', foreignKey: 'authorId'}); + * ``` + * + * ``` + * Book.hasMany(Chapter); + * ``` + * Or, equivalently: + * ``` + * Book.hasMany('chapters', {model: Chapter}); + * ``` * @param {Relation} anotherClass Class to has many - * @param {Object} params Configuration {as:, foreignKey:} + * @options {Object} parameters Configuration parameters + * @property {String} as + * @property {String} foreignKey Property name of foreign key field. + * @property {Object} model Model object */ Relation.hasMany = function hasMany(anotherClass, params) { var thisClassName = this.modelName; @@ -218,9 +231,28 @@ Relation.hasMany = function hasMany(anotherClass, params) { }; /** - * Declare "belongsTo" relation. + * Declare "belongsTo" relation that sets up a one-to-one connection with another model, such that each + * instance of the declaring model "belongs to" one instance of the other model. * - * **Examples** + * For example, if an application includes users and posts, and each post can be written by exactly one user. + * The following code specifies that `Post` has a reference called `author` to the `User` model via the `userId` property of `Post` + * as the foreign key. + * ``` + * Post.belongsTo(User, {as: 'author', foreignKey: 'userId'}); + * ``` + * You can then access the author in one of the following styles. + * Get the User object for the post author asynchronously: + * ``` + * post.author(callback); + * ``` + * Get the User object for the post author synchronously: + * ``` + * post.author(); + * Set the author to be the given user: + * ``` + * post.author(user) + * ``` + * Examples: * * Suppose the model Post has a *belongsTo* relationship with User (the author of the post). You could declare it this way: * ```js @@ -244,7 +276,9 @@ 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} params Configuration {as: 'propertyName', foreignKey: 'keyName'} + * @param {Object} Parameters Configuration parameters + * @property {String} as Can be 'propertyName' + * @property {String} foreignKey Name of foreign key property. * */ Relation.belongsTo = function (anotherClass, params) { @@ -389,15 +423,34 @@ Relation.belongsTo = function (anotherClass, params) { }; /** - * Many-to-many relation - * - * For example, this creates connection model 'PostTag': - * ```js - * Post.hasAndBelongsToMany('tags'); - * ``` + * A hasAndBelongsToMany relation creates a direct many-to-many connection with another model, with no intervening model. + * For example, if your application includes users and groups, with each group having many users and each user appearing + * in many groups, you could declare the models this way: + * ``` + * User.hasAndBelongsToMany('groups', {model: Group, foreignKey: 'groupId'}); + * ``` + * Then, to get the groups to which the user belongs: + * ``` + * user.groups(callback); + * ``` + * Create a new group and connect it with the user: + * ``` + * user.groups.create(data, callback); + * ``` + * Connect an existing group with the user: + * ``` + * user.groups.add(group, callback); + * ``` + * Remove the user from the group: + * ``` + * user.groups.remove(group, callback); + * ``` + * * @param {String|Function} anotherClass - target class to hasAndBelongsToMany or name of * the relation - * @param {Object} params - configuration {as: String, foreignKey: *, model: ModelClass} + * @options {Object} params - configuration {as: String, foreignKey: *, model: ModelClass} + * @property {Object} model Model name + * @property {String} foreignKey Property name of foreign key field. */ Relation.hasAndBelongsToMany = function hasAndBelongsToMany(anotherClass, params) { params = params || {}; 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", 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;