Skip to content

Commit

Permalink
Allow the retrieval of partial objects
Browse files Browse the repository at this point in the history
  • Loading branch information
notheotherben committed Apr 13, 2014
1 parent 0194b14 commit 8a26f0e
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 14 deletions.
26 changes: 22 additions & 4 deletions lib/Instance.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ var ObjectID = require('mongodb').ObjectID,

(require.modules || {}).Instance = module.exports = Instance;

function Instance(model, doc, isNew) {
function Instance(model, doc, isNew, isPartial) {
/// <signature>
/// <summary>Creates a new wrapper around a database document</summary>
/// <param name="model" type="Model">The model for which the instance should be created</param>
Expand All @@ -21,6 +21,12 @@ function Instance(model, doc, isNew) {
/// <param name="doc" type="Object">The document from the database which is to be wrapped</param>
/// <param name="isNew" type="Boolean>Should be true if this instance is not present in the database</param>
/// </signature>
/// <signature>
/// <summary>Creates a new wrapper around a database document</summary>
/// <param name="model" type="Model">The model for which the instance should be created</param>
/// <param name="doc" type="Object">The document from the database which is to be wrapped</param>
/// <param name="isPartial" type="Boolean>Should be true if this instance is only a partial representation of what's in the database</param>
/// </signature>

"use strict";

Expand All @@ -31,6 +37,7 @@ function Instance(model, doc, isNew) {
this.__state = {
model: model,
isNew: isNew || false,
isPartial: isPartial || false,
original: _.cloneDeep(doc),
modified: _.cloneDeep(doc)
};
Expand Down Expand Up @@ -115,14 +122,15 @@ Instance.prototype.save = function(conditions, changes, callback) {
if(err) return onError(err);

this.__state.isNew = false;
this.__state.isPartial = false;
this.__state.model.onRetrieved(conditions, created[0], callback || function() { }, (function(value) {
this.__state.model.fromSource(value);
this.__state.original = _.cloneDeep(value);
this.__state.modified = _.cloneDeep(value);
this.__extendSchema();
this.emit('retrieved', this);
return this;
}).bind(this));
}).bind(this), { partial: this.__state.isPartial });
}).bind(this));
}).bind(this));
}
Expand Down Expand Up @@ -165,6 +173,7 @@ Instance.prototype.save = function(conditions, changes, callback) {

this.__state.model.onRetrieved(conditions, latest, callback || function () { }, (function (value) {
this.__state.model.fromSource(value);
this.__state.isPartial = false;
this.__state.original = _.cloneDeep(value);
this.__state.modified = _.cloneDeep(value);
this.__extendSchema();
Expand Down Expand Up @@ -194,9 +203,17 @@ Instance.prototype.refresh = Instance.prototype.update = function(callback) {
var conditions = this.__state.model.uniqueConditions(this.__state.original);
this.__state.model.collection.findOne(conditions, (function(err, latest) {
if(err) return onError(err);
if(!latest) {
this.__state.isPartial = false;
this.__state.isNew = true;
this.__state.original = _.cloneDeep(this.__state.modified);
return this;
}

this.__state.model.onRetrieved(conditions, latest, callback || function() { }, (function(value) {
this.__state.model.fromSource(value);
this.__state.isNew = false;
this.__state.isPartial = false;
this.__state.original = _.cloneDeep(value);
this.__state.modified = _.cloneDeep(value);
this.__extendSchema();
Expand Down Expand Up @@ -315,12 +332,13 @@ Instance.forModel = function(model) {
/// <summary>Creates an instance wrapper for the specified model</summary>
/// <param name="model" type="Model">The model which the instance wraps</param>

function ModelInstance(doc, isNew) {
function ModelInstance(doc, isNew, isPartial) {
/// <summary>Creates a new model instance for the specified document</summary>
/// <param name="doc" type="Object">The document which the instance should wrap</param>
/// <param name="isNew" type="Boolean" optional="true">Whether or not the document was sourced from the database</param>
/// <param name="isPartial" type="Boolean" optional="true">Whether or not the document represents a partial version of the database document</param>

Instance.call(this, model, doc, isNew);
Instance.call(this, model, doc, isNew, isPartial);
}

inherit(ModelInstance, Instance);
Expand Down
42 changes: 32 additions & 10 deletions lib/Model.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,11 @@ function Model(database, collection, schema, options) {
new Concoction.Convert({
id: {
apply: function (value) { return (value && value.id) ? new ObjectID(value.id).toHexString() : value; },
reverse: function (value) { return value ? ObjectID.createFromHexString(value) : undefined; }
reverse: function (value) {
if(value === null || value === undefined) return undefined;
if(value && /^[a-f0-9]{24}$/.test(value)) return ObjectID.createFromHexString(value);
return value;
}
}
})
]
Expand Down Expand Up @@ -172,7 +176,7 @@ Model.prototype.downstreamID = function(id) {
} else return _id;
};

Model.prototype.wrap = function (document, isNew) {
Model.prototype.wrap = function (document, isNew, isPartial) {
/// <signature>
/// <summary>Wraps the given database object in this model's Instance wrapper</summary>
/// <param name="document" type="Object">The database object to be wrapped by this model</param>
Expand All @@ -184,8 +188,15 @@ Model.prototype.wrap = function (document, isNew) {
/// <param name="isNew" type="Boolean">Whether or not this instance is new (not in the database)</param>
/// <returns value="new this.Instance(document, isNew)"/>
/// </signature>
/// <signature>
/// <summary>Wraps the given database object in this model's Instance wrapper</summary>
/// <param name="document" type="Object">The database object to be wrapped by this model</param>
/// <param name="isNew" type="Boolean">Whether or not this instance is new (not in the database)</param>
/// <param name="isPartial" type="Boolean">Whether or not this instance is only a partial representation of the database version</param>
/// <returns value="new this.Instance(document, isNew, isPartial)"/>
/// </signature>

return new this.Instance(document, isNew);
return new this.Instance(document, isNew, isPartial);
};

Model.prototype.onRetrieved = function(conditions, results, callback, wrapper, options) {
Expand Down Expand Up @@ -213,7 +224,8 @@ Model.prototype.onRetrieved = function(conditions, results, callback, wrapper, o

_.defaults(options, {
wrap: true,
cache: true
cache: true,
partial: false
});

var returnArray = Array.isArray(results);
Expand Down Expand Up @@ -244,15 +256,15 @@ Model.prototype.onRetrieved = function(conditions, results, callback, wrapper, o

var cacheDoc = _.cloneDeep(target);

var wrapped = options.wrap ? wrapper(target) : this.fromSource(target);
var wrapped = options.wrap ? wrapper(target, false, options.partial) : this.fromSource(target);

doHook(this.options.hooks.ready, wrapped, (function(err) {
if(err) {
this.emit('error', err);
return done(err);
}
this.emit('ready', wrapped);
if(options.cache)
if(options.cache && !options.partial)
return this.cache.store(conditions, cacheDoc, function() {
return done(null, wrapped);
});
Expand Down Expand Up @@ -366,17 +378,22 @@ Model.prototype.find = function (conditions, options, callback) {
wrap: true
});

if(options.fields)
this.toSource(options.fields);

var $ = this;
if (!_.isPlainObject(conditions)) conditions = this.downstreamID(conditions);
this.toSource(conditions);

this.collection.find(conditions, { limit: options.limit, sort: options.sort, skip: options.skip }).toArray((function (err, results) {
this.collection.find(conditions,
{ limit: options.limit, sort: options.sort, skip: options.skip, fields: options.fields })
.toArray((function (err, results) {
if (err) {
this.emit('error', err);
return callback(err);
}
if (!results) return callback(null, null);
return $.onRetrieved(conditions, results, callback, null, { wrap: options.wrap, cache: false });
return $.onRetrieved(conditions, results, callback, null, { wrap: options.wrap, cache: false, partial: !!options.fields });
}).bind(this));
};

Expand Down Expand Up @@ -417,15 +434,20 @@ Model.prototype.findOne = Model.prototype.get = function (conditions, options, c
if (!_.isPlainObject(conditions)) conditions = this.downstreamID(conditions);
this.toSource(conditions);

if(options.fields)
this.toSource(options.fields);

var fromDB = (function() {
this.collection.findOne(conditions, { limit: options.limit, sort: options.sort, skip: options.skip }, (function (err, results) {
this.collection.findOne(conditions,
{ limit: options.limit, sort: options.sort, skip: options.skip, fields: options.fields },
(function (err, results) {
if (err) {
this.emit('error', err);
return callback(err);
}
if (!results) return callback(null, null);

return this.onRetrieved(conditions, results, callback, null, { wrap: options.wrap, cache: options.cache });
return this.onRetrieved(conditions, results, callback, null, { wrap: options.wrap, cache: options.cache, partial: !!options.fields });
}).bind(this));
}).bind(this);

Expand Down
42 changes: 42 additions & 0 deletions test/findOne.js
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,48 @@ describe('orm', function () {
});
});
});

describe('with partial results', function() {
before(function(done) {
model = new Model(db, 'model', {
name: /.+/,
description: String
}, {

});

model.remove(function(err) {
if(err) return done(err);

model.create({
name: 'Demo1',
description: 'Demonstration 1'
}, function(err, instance) {
if(err) return done(err);
return done();
});
});
});

it('should return just the selected fields', function(done) {
model.findOne({ name: 'Demo1' }, { fields: { id: 1, name: 1 }}, function(err, doc) {
if(err) return done(err);
should.exist(doc);
doc.document.should.have.ownProperty('name').and.eql('Demo1');
doc.document.should.not.have.ownProperty('description');
done();
});
});

it('should set the isPartial flag on its instances', function(done) {
model.findOne({ name: 'Demo1' }, { fields: { id: 1, name: 1 }}, function(err, doc) {
if(err) return done(err);
should.exist(doc);
doc.__state.isPartial.should.be.true;
done();
});
});
});
});
});
});
Expand Down

0 comments on commit 8a26f0e

Please sign in to comment.