Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,17 @@ store.get('users', {
});
```

### Internal Caching

Store supports internal caching. In order to get this functionality up and running you need to implement the Cache interface found in `lib/cache.js` and pass an instance of your implementation upon Store instastiation. Example:

```javascript
var cache = new MyCache({ ttl: 12450, other: 'options' });
var store = new Store(cache);
```

Store use the Cache upon create/update & get of objects. **Attention**: Store.delete does not clean item from cache. You need to rely on ttl for this.

## Schemas

### Define a primary key
Expand Down
17 changes: 17 additions & 0 deletions lib/cache.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
function Cache(options) {
this.options = options;
};

Cache.prototype.get = function(key) {
return null;
};

Cache.prototype.set = function(key, _) {
return false;
};

Cache.prototype.delete = function(key) {
return false;
};

module.exports = Cache;
6 changes: 5 additions & 1 deletion lib/schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ var Schema = function(schema) {
this.primaryKey = primaryKey.pop();
};

Schema.prototype.getPkOfInstance = function(instance){
return instance[this.primaryKey] || null;
};

Schema.prototype.create = function(obj, schema){
var that = this;
var instance = {};
Expand Down Expand Up @@ -137,4 +141,4 @@ Schema.prototype.create = function(obj, schema){
});
};

module.exports = Schema;
module.exports = Schema;
42 changes: 31 additions & 11 deletions lib/store.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
var Promise = require('bluebird');
var util = require('util');
var Schema = require('./schema');
var Cache = require('./cache');

var Store = function() {
function cacheKey(model, pk) {
return util.format('%s:%s', model, pk);
};

function Store(cache) {
this.cache = (cache instanceof Cache) ? cache : false;
this.models = {};
};

Expand All @@ -26,31 +32,45 @@ Store.prototype.getModel = function(modelName) {
};

Store.prototype.get = function(modelName, pk) {
var key = cacheKey(modelName, pk);
if (this.cache && (cached=this.cache.get(key))) {
return cached;
}
var model = this.getModel(modelName);
return this._get(model, pk);
var instance = this._get(model, pk);
if (this.cache) {
this.cache.set(key, instance);
}
return instance;
};

Store.prototype.filter = function(modelName, query) {
var model = this.getModel(modelName);
return this._filter(model, query);
};

function afterCreate(action, modelName, model) {
return function(instance) {
var promise = this._set(model, instance, action);
var pk = model.schema.getPkOfInstance(instance);
if (this.cache && pk !== null) {
var key = cacheKey(modelName, pk);
this.cache.set(key, promise);
}
return promise;
};
};

Store.prototype.create = function(modelName, obj) {
obj = obj || {};
var that = this;
var model = this.getModel(modelName);
return model.schema.create(obj).then(function(instance) {
return that._set(model, instance, 'create');
});
return model.schema.create(obj).then(afterCreate('create', modelName, model).bind(this));
};

Store.prototype.update = function(modelName, obj) {
obj = obj || {};
var that = this;
var model = this.getModel(modelName);
return model.schema.create(obj).then(function(instance) {
return that._set(model, instance, 'update');
});
return model.schema.create(obj).then(afterCreate('update', modelName, model).bind(this));
};

Store.prototype.delete = function(modelName, query) {
Expand Down Expand Up @@ -104,4 +124,4 @@ Store.prototype._delete = function(query) {
return Promise.reject(new Error('Store.prototype._delete(query) is not implemented'));
};

module.exports = Store;
module.exports = Store;
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "stormer",
"version": "0.8.0",
"version": "0.9.0",
"description": "The flexible Node.js ORM",
"main": "index.js",
"directories": {
Expand Down
23 changes: 23 additions & 0 deletions test/mock/mycache.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
var Cache = require('../../lib/cache');
var util = require('util');

function MyCache() {
Cache.call(this);
this._internal = new Map();
};

util.inherits(MyCache, Cache);

MyCache.prototype.get = function(key) {
return this._internal.get(key);
};

MyCache.prototype.set = function(key, obj) {
return this._internal.set(key, obj);
};

MyCache.prototype.delete = function(key) {
return this._internal.delete(key);
};

module.exports = MyCache;
26 changes: 26 additions & 0 deletions test/schema.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -475,5 +475,31 @@ describe('Schema Tests', function() {
});

});

describe('Schema.prototype.getPkOfInstance should', function() {

it('return null if not primary key is set for schema', function(done) {
var schema = new Schema({ pk: 'String' });
(schema.getPkOfInstance({}) === null).should.equal(true);
(schema.getPkOfInstance({ pk: '12345' }) === null).should.equal(true);
done();
});

it('return null instance has no primary key', function(done) {
var schema = new Schema({ pk: { type: 'String', primaryKey: true } });
(schema.getPkOfInstance({}) === null).should.equal(true);
(schema.getPkOfInstance({ foo: '12345' }) === null).should.equal(true);
done();
});

it('return primary key of instance', function(done) {
var schema = new Schema({ pk: { type: 'String', primaryKey: true } });
schema.getPkOfInstance({ pk: '12345' }).should.equal('12345');
(schema.getPkOfInstance({ pk: null }) === null).should.equal(true);
(schema.getPkOfInstance({ pk: undefined }) === null).should.equal(true);
done();
});

});

});
108 changes: 106 additions & 2 deletions test/store.spec.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
var chai = require('chai');
var sinon = require('sinon');
var Store = require('../lib/store');
var MyCache = require('./mock/mycache');
chai.should();

var sandbox = sinon.sandbox.create();
Expand Down Expand Up @@ -62,7 +63,7 @@ describe('Store Tests', function() {
var fakeObj = { pk: '1234'};
var createStub = sandbox.stub(store, '_set').returns(Promise.resolve());

var model = store.define('myModel', { pk: 'String' });
var model = store.define('myModel', { pk: { type: 'String', primaryKey: true } });
store.create('myModel', fakeObj).then(function() {
createStub.called.should.be.true;
createStub.calledWith(model, fakeObj, 'create').should.be.true;
Expand All @@ -74,7 +75,7 @@ describe('Store Tests', function() {
var fakeObj = { pk: '1234'};
var updateSpy = sandbox.stub(store, '_set').returns(Promise.resolve());

var model = store.define('myModel', { pk: 'String' });
var model = store.define('myModel', { pk: { type: 'String', primaryKey: true } });
store.update('myModel', fakeObj).then(function() {
updateSpy.called.should.be.true;
updateSpy.calledWith(model, fakeObj, 'update').should.be.true;
Expand All @@ -93,5 +94,108 @@ describe('Store Tests', function() {
done();
}).catch(done);
});

it('Store.prototype._filter() should return a Promise rejected', function(done) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

store._filter().catch(function(err) {
err.message.should.equal('Store.prototype._filter(model, query) is not implemented');
done();
});
});

it('Store.prototype._get() should return a Promise rejected', function(done) {
store._get().catch(function(err) {
err.message.should.equal('Store.prototype._get(model, pk) is not implemented');
done();
});
});

it('Store.prototype._set() should return a Promise rejected', function(done) {
store._set().catch(function(err) {
err.message.should.equal('Store.prototype._set(model, obj, operation) is not implemented');
done();
});
});

it('Store.prototype._delete() should return a Promise rejected', function(done) {
store._delete().catch(function(err) {
err.message.should.equal('Store.prototype._delete(query) is not implemented');
done();
});
});

describe('Cache Integration should', function() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Medium] Can we put the cache test in a seprate file (to be consistent with the project structure)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here I am actually testing the integration of cache in store and not the actual cache implementation as it does not exist.

I think it make more sense for this to be here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cool


var cache;

beforeEach(function() {
cache = new MyCache();
});

it('have cache objext', function(done) {
var store = new Store();
store.cache.should.equal(false);

var store = new Store(cache);
store.cache.should.equal(cache);
done();
});

it('return from cache when get', function(done) {
var store = new Store(cache);

var pk = '1234';
var instance = Promise.resolve({ foo: 'bar' });
var getStub = sandbox.stub(store, '_get').returns(instance);

var model = store.define('myModel', {});

store.get('myModel', pk).then(function(expected) {
store.get('myModel', pk).then(function(actual) {
(getStub.calledOnce == true).should.equal(true);
(getStub.calledWith(model, pk) == true).should.equal(true);
actual.should.equal(expected);
done();
}).catch(done);
}).catch(done);
});

it('set to cache when create', function(done) {
var obj = { pk: '1234' };
var resolved = Promise.resolve(obj);

var store = new Store(cache);
var model = store.define('myModel', { pk: { type: 'String', primaryKey: true } });

var createStub = sandbox.stub(store, '_set').returns(resolved);
var getStub = sandbox.stub(store, '_get').returns(resolved);

store.create('myModel', obj).then(function(actual) {
store.get('myModel', obj.pk).then(function(expected) {
getStub.called.should.equal(false);
expected.should.equal(actual);
done();
}).catch(done);
}).catch(done);
});

it('set to cache when update', function(done) {
var obj = { pk: '1234' };
var resolved = Promise.resolve(obj);

var store = new Store(cache);
var model = store.define('myModel', { pk: { type: 'String', primaryKey: true } });

var createStub = sandbox.stub(store, '_set').returns(resolved);
var getStub = sandbox.stub(store, '_get').returns(resolved);

store.update('myModel', obj).then(function(actual) {
store.get('myModel', obj.pk).then(function(expected) {
getStub.called.should.equal(false);
expected.should.equal(actual);
done();
}).catch(done);
}).catch(done);
});
});

});