diff --git a/lib/connectors/memory.js b/lib/connectors/memory.js index 3490dfe3..04b0ed98 100644 --- a/lib/connectors/memory.js +++ b/lib/connectors/memory.js @@ -78,6 +78,11 @@ Memory.prototype.getCollection = function(model) { return model; } +Memory.prototype.initCollection = function(model) { + this.collection(model, {}); + this.collectionSeq(model, 1); +} + Memory.prototype.collection = function(model, val) { model = this.getCollection(model); if (arguments.length > 1) this.cache[model] = val; @@ -181,10 +186,7 @@ Memory.prototype.saveToFile = function (result, callback) { Memory.prototype.define = function defineModel(definition) { this.constructor.super_.prototype.define.apply(this, [].slice.call(arguments)); var m = definition.model.modelName; - if(!this.collection(m)) { - this.collection(m, {}); - this.collectionSeq(m, 1); - } + if(!this.collection(m)) this.initCollection(m); }; Memory.prototype.create = function create(model, data, callback) { @@ -612,6 +614,16 @@ Memory.prototype.buildNearFilter = function (filter) { // noop } +Memory.prototype.automigrate = function (models, cb) { + if (typeof models === 'function') cb = models, models = []; + if (models.length === 0) models = Object.keys(this._models); + var self = this; + models.forEach(function(m) { + self.initCollection(m); + }); + if (cb) cb(); +} + function merge(base, update) { if (!base) { return update; diff --git a/lib/dao.js b/lib/dao.js index 1d308f73..d5277b5e 100644 --- a/lib/dao.js +++ b/lib/dao.js @@ -79,7 +79,10 @@ DataAccessObject.applyProperties = function(data) { }; DataAccessObject.applyScope = function(cond) { - + var scope = this.definition.settings.scope; + if (typeof scope === 'object') { + mergeQuery(cond, scope || {}, this.definition.settings.scoping); + } }; /** @@ -330,17 +333,7 @@ DataAccessObject.exists = function exists(id, cb) { */ DataAccessObject.findById = function find(id, cb) { if (stillConnecting(this.getDataSource(), this, arguments)) return; - - this.getDataSource().connector.find(this.modelName, id, function (err, data) { - var obj = null; - if (data) { - if (!getIdValue(this, data)) { - setIdValue(this, data, id); - } - obj = new this(data, {applySetters: false, persisted: true}); - } - cb(err, obj); - }.bind(this)); + this.findOne(byIdQuery(this, id), cb); }; DataAccessObject.findByIds = function(ids, cond, cb) { @@ -702,7 +695,7 @@ DataAccessObject.find = function find(query, cb) { var self = this; query = query || {}; - + try { this._normalize(query); } catch (err) { @@ -711,6 +704,8 @@ DataAccessObject.find = function find(query, cb) { }); } + this.applyScope(query); + var near = query && geo.nearFilter(query.where); var supportsGeo = !!this.getDataSource().connector.buildNearFilter; diff --git a/lib/model.js b/lib/model.js index 7bb19eb7..7e7a66f2 100644 --- a/lib/model.js +++ b/lib/model.js @@ -134,7 +134,7 @@ ModelBaseClass.prototype._initProperties = function (data, options) { } if (properties[p]) { // Managed property - if (applySetters) { + if (applySetters || properties[p].id) { self[p] = propVal; } else { self.__data[p] = propVal; diff --git a/lib/utils.js b/lib/utils.js index bbde56fc..4e317058 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -56,14 +56,17 @@ function setScopeValuesFromWhere(data, where, targetModel) { * Merge query parameters * @param {Object} base The base object to contain the merged results * @param {Object} update The object containing updates to be merged + * @param {Object} spec Optionally specifies parameters to exclude (set to false) * @returns {*|Object} The base object * @private */ -function mergeQuery(base, update) { +function mergeQuery(base, update, spec) { if (!update) { return; } + spec = spec || {}; base = base || {}; + if (update.where && Object.keys(update.where).length > 0) { if (base.where && Object.keys(base.where).length > 0) { base.where = {and: [base.where, update.where]}; @@ -73,7 +76,7 @@ function mergeQuery(base, update) { } // Merge inclusion - if (update.include) { + if (spec.include !== false && update.include) { if (!base.include) { base.include = update.include; } else { @@ -82,30 +85,36 @@ function mergeQuery(base, update) { base.include[update.include] = saved; } } - if (update.collect) { + + if (spec.collect !== false && update.collect) { base.collect = update.collect; } - + + // Overwrite fields + if (spec.fields !== false && update.fields !== undefined) { + base.fields = update.fields; + } + // set order - if (!base.order && update.order) { + if ((!base.order || spec.order === false) && update.order) { base.order = update.order; } // overwrite pagination - if (update.limit !== undefined) { + if (spec.limit !== false && update.limit !== undefined) { base.limit = update.limit; } - if (update.skip !== undefined) { + + var skip = spec.skip !== false && spec.offset !== false; + + if (skip && update.skip !== undefined) { base.skip = update.skip; } - if (update.offset !== undefined) { + + if (skip && update.offset !== undefined) { base.offset = update.offset; } - - // Overwrite fields - if (update.fields !== undefined) { - base.fields = update.fields; - } + return base; } diff --git a/test/default-scope.test.js b/test/default-scope.test.js new file mode 100644 index 00000000..01d218b1 --- /dev/null +++ b/test/default-scope.test.js @@ -0,0 +1,210 @@ +// This test written in mocha+should.js +var should = require('./init.js'); +var async = require('async'); + +var db, Product, Tool, Widget; + +// This test requires a connector that can +// handle a custom collection or table name + +describe('default scope', function () { + + before(function (done) { + db = getSchema(); + + Product = db.define('Product', { + name: String, + kind: String, + description: String + }, { + scope: { order: 'name' }, + }); + + Tool = db.define('Tool', { + name: String, + kind: String, + description: String + }, { + base: 'Product', + scope: { where: { kind: 'tool' }, order: 'name' }, + mongodb: { collection: 'Product' }, + memory: { collection: 'Product' } + }); + + Widget = db.define('Widget', { + name: String, + kind: String, + description: String + }, { + base: 'Product', + scope: { where: { kind: 'widget' }, order: 'name' }, + mongodb: { collection: 'Product' }, + memory: { collection: 'Product' } + }); + + db.automigrate(done); + }); + + describe('manipulation', function() { + + var ids = {}; + + before(function(done) { + db.automigrate(done); + }); + + it('should return a scoped instance', function() { + var p = new Tool({name: 'Product A', kind:'ignored'}); + p.name.should.equal('Product A'); + p.kind.should.equal('tool'); + p.setAttributes({ kind: 'ignored' }); + p.kind.should.equal('tool'); + + p.setAttribute('kind', 'other'); // currently not enforced + p.kind.should.equal('other'); + }); + + it('should create a scoped instance - tool', function(done) { + Tool.create({name: 'Product A', kind: 'ignored'}, function(err, p) { + should.not.exist(err); + p.name.should.equal('Product A'); + p.kind.should.equal('tool'); + ids.productA = p.id; + done(); + }); + }); + + it('should create a scoped instance - widget', function(done) { + Widget.create({name: 'Product B', kind: 'ignored'}, function(err, p) { + should.not.exist(err); + p.name.should.equal('Product B'); + p.kind.should.equal('widget'); + ids.productB = p.id; + done(); + }); + }); + + it('should update a scoped instance - updateAttributes', function(done) { + Tool.findById(ids.productA, function(err, p) { + p.updateAttributes({description: 'A thing...', kind: 'ingored'}, function(err, inst) { + should.not.exist(err); + p.name.should.equal('Product A'); + p.kind.should.equal('tool'); + p.description.should.equal('A thing...'); + done(); + }); + }); + }); + + it('should update a scoped instance - save', function(done) { + Tool.findById(ids.productA, function(err, p) { + p.description = 'Something...'; + p.kind = 'ignored'; + p.save(function(err, inst) { + should.not.exist(err); + p.name.should.equal('Product A'); + p.kind.should.equal('tool'); + p.description.should.equal('Something...'); + Tool.findById(ids.productA, function(err, p) { + p.kind.should.equal('tool'); + done(); + }); + }); + }); + }); + + it('should update a scoped instance - updateOrCreate', function(done) { + var data = {id: ids.productA, description: 'Anything...', kind: 'ingored'}; + Tool.updateOrCreate(data, function(err, p) { + should.not.exist(err); + p.name.should.equal('Product A'); + p.kind.should.equal('tool'); + p.description.should.equal('Anything...'); + done(); + }); + }); + + }); + + describe('queries', function() { + + var ids = {}; + + before(function (done) { + db.automigrate(function(err) { + async.series([ + function(next) { + Tool.create({name: 'Tool Z'}, function(err, inst) { + ids.toolZ = inst.id; + next(); + }); + }, + function(next) { + Widget.create({name: 'Widget Z'}, function(err, inst) { + ids.widgetZ = inst.id; + next(); + }); + }, + function(next) { + Tool.create({name: 'Tool A'}, function(err, inst) { + ids.toolA = inst.id; + next(); + }); + }, + function(next) { + Widget.create({name: 'Widget A'}, function(err, inst) { + ids.widgetA = inst.id; + next(); + }); + } + ], done); + }); + }); + + it('should apply default scope - order', function(done) { + Product.find(function(err, products) { + should.not.exist(err); + products.should.have.length(4); + products[0].name.should.equal('Tool A'); + products[1].name.should.equal('Tool Z'); + products[2].name.should.equal('Widget A'); + products[3].name.should.equal('Widget Z'); + done(); + }); + }); + + it('should apply default scope - order override', function(done) { + Product.find({ order: 'name DESC' }, function(err, products) { + should.not.exist(err); + products.should.have.length(4); + products[0].name.should.equal('Widget Z'); + products[1].name.should.equal('Widget A'); + products[2].name.should.equal('Tool Z'); + products[3].name.should.equal('Tool A'); + done(); + }); + }); + + it('should apply default scope - where + order (tool)', function(done) { + Tool.find(function(err, products) { + should.not.exist(err); + products.should.have.length(2); + products[0].name.should.equal('Tool A'); + products[1].name.should.equal('Tool Z'); + done(); + }); + }); + + it('should apply default scope - where + order (widget)', function(done) { + Widget.find({ order: 'name DESC' }, function(err, products) { + should.not.exist(err); + products.should.have.length(2); + products[0].name.should.equal('Widget Z'); + products[1].name.should.equal('Widget A'); + done(); + }); + }); + + }); + +}); \ No newline at end of file diff --git a/test/relations.test.js b/test/relations.test.js index 12e78d68..5315e677 100644 --- a/test/relations.test.js +++ b/test/relations.test.js @@ -335,7 +335,7 @@ describe('relations', function () { physician.patients.findById(id, function (err, ch) { should.not.exist(err); should.exist(ch); - ch.id.should.equal(id); + ch.id.should.eql(id); done(); }); } @@ -387,7 +387,7 @@ describe('relations', function () { physician.patients.findById(id, function (err, ch) { should.not.exist(err); should.exist(ch); - ch.id.should.equal(id); + ch.id.should.eql(id); ch.name.should.equal('aa'); done(); });