Skip to content

Commit

Permalink
scheduled caching
Browse files Browse the repository at this point in the history
  • Loading branch information
Benjamin Malley committed Sep 12, 2014
1 parent ae44463 commit a3291a6
Show file tree
Hide file tree
Showing 7 changed files with 151 additions and 24 deletions.
4 changes: 3 additions & 1 deletion app/scripts/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,13 @@ angular.module('deckApp', [
'angularSpinner',
'deckApp.templates'
])
.run(function($state, $rootScope, $log, $exceptionHandler) {
.run(function($state, $rootScope, $log, $exceptionHandler, $http, scheduledCache) {
// This can go away when the next version of ui-router is available (0.2.11+)
// for now, it's needed because ui-sref-active does not work on parent states
// and we have to use ng-class. It's gross.
//

$http.defaults.cache = scheduledCache('http', 10);
$rootScope.subscribeTo = function(observable) {
this.subscribed = {
data: undefined
Expand Down
26 changes: 3 additions & 23 deletions app/scripts/services/accountService.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,13 @@ var angular = require('angular');
angular.module('deckApp')
.factory('accountService', function(settings, Restangular, $q) {

var detailsCache = {},
credentialsCache = [];

var credentialsEndpoint = Restangular.withConfig(function(RestangularConfigurer) {
RestangularConfigurer.setDefaultHttpFields({ cache: true });
RestangularConfigurer.setBaseUrl(settings.credentialsUrl);
});

function listAccounts() {
var deferred = $q.defer();
if (credentialsCache.length) {
deferred.resolve(credentialsCache);
} else {
credentialsEndpoint.all('credentials').getList().then(function(list) {
credentialsCache = list;
deferred.resolve(list);
});
}
return deferred.promise;
return credentialsEndpoint.all('credentials').getList();
}

function getRegionsKeyedByAccount() {
Expand All @@ -40,16 +29,7 @@ angular.module('deckApp')
}

function getAccountDetails(accountName) {
var deferred = $q.defer();
if (detailsCache[accountName]) {
deferred.resolve(detailsCache[accountName]);
} else {
credentialsEndpoint.one('credentials', accountName).get().then(function(details) {
detailsCache[accountName] = details;
deferred.resolve(details);
});
}
return deferred.promise;
credentialsEndpoint.one('credentials', accountName).get();
}

function getRegionsForAccount(accountName) {
Expand Down
39 changes: 39 additions & 0 deletions app/scripts/services/scheduledCache.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
'use strict';

var angular = require('angular');
angular.module('deckApp')
.factory('scheduledCache', function($cacheFactory, scheduler) {
// returns a cache that is cleared according to the scheduler
return function(id, cycles) {
function ScheduledCache(id, cycles) {
var that = this;
that.cycles = cycles || 0;

that.cache = $cacheFactory(id);

that.disposable = scheduler.get()
.skip(that.cycles)
.subscribe(function() {
that.cache.removeAll();
});

that.info = that.cache.info;
that.put = that.cache.put;
that.get = that.cache.get;
that.remove = that.cache.remove;
that.removeAll = that.cache.removeAll;

that.destroy = function() {
that.disposable.dispose();
that.cache.destroy();
delete that.disposable;
delete that.cache;
};

return this;
}

return new ScheduledCache(id, cycles);
};
});

1 change: 1 addition & 0 deletions app/scripts/services/scheduler.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ angular.module('deckApp')
});

return {
get: function() { return scheduler; },
subscribe: scheduler.subscribe.bind(scheduler),
scheduleImmediate: scheduler.onNext.bind(scheduler),
scheduleOnCompletion: function(promise) {
Expand Down
2 changes: 2 additions & 0 deletions karma.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ module.exports = function(config) {
'.tmp/scripts/templates.js',
'dist/scripts/settings/settings.js',
'node_modules/angular-mocks/angular-mocks.js',
'node_modules/rx/dist/rx.all.js',
'test/poly/**/*.js',
'test/mock/**/*.js',
'test/spec/**/*.js'
],
Expand Down
25 changes: 25 additions & 0 deletions test/poly/bindpolyfill.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
'use strict';
if (!Function.prototype.bind) {
Function.prototype.bind = function (oThis) {
if (typeof this !== "function") {
// closest thing possible to the ECMAScript 5
// internal IsCallable function
throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
}

var aArgs = Array.prototype.slice.call(arguments, 1),
fToBind = this,
fNOP = function () {},
fBound = function () {
return fToBind.apply(this instanceof fNOP && oThis
? this
: oThis,
aArgs.concat(Array.prototype.slice.call(arguments)));
};

fNOP.prototype = this.prototype;
fBound.prototype = new fNOP();

return fBound;
};
}
78 changes: 78 additions & 0 deletions test/spec/services/scheduledCache.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
'use strict';

describe('Service: scheduledCache', function() {
beforeEach(function() {
var subject = new Rx.Subject();
this.subject = subject;
var scheduler = {
get: angular.noop,
};
this.scheduler = scheduler;
spyOn(scheduler, 'get').andReturn(subject);

module('deckApp');
module(function($provide) {
$provide.value('scheduler', scheduler);
});

inject(function(scheduledCache, $cacheFactory) {
this.scheduledCache = scheduledCache;
this.$cacheFactory = $cacheFactory;
});
});

describe('initializing the cache', function() {

it('creates an angular cache at the specified id', function() {
var cache = this.scheduledCache('id');
expect(this.$cacheFactory.get('id').info().id).toEqual(cache.info().id);
});

it('allows an optional parameter to specify the refresh interval', function() {
var cache = this.scheduledCache('id', 2);
expect(cache.cycles).toBe(2);
});

it('defaults to refreshing every cycle', function() {
var cache = this.scheduledCache('id');
expect(cache.cycles).toBe(0);
});

it('gets the global scheduler on initialization', function() {
this.scheduledCache('id');
expect(this.scheduler.get.callCount).toEqual(1);
});

it('will throw if the same id is initialized twice', function() {
this.scheduledCache('id');
expect(function() {
this.scheduledCache('id');
}).toThrow();
});

it('shares the same id space as $cacheFactory', function() {
this.scheduledCache('id');
expect(function() {
this.$cacheFactory('id');
}.bind(this)).toThrow();
});
});

describe('the schedule', function() {
it('clears the cache once every n cycles', function() {
var cache = this.scheduledCache('id');
cache.put('foo', 'bar');
expect(cache.get('foo')).toEqual('bar');
this.subject.onNext();
expect(cache.get('foo')).toBeUndefined();

var cache = this.scheduledCache('id2', 1);
cache.put('foo', 'bar');
expect(cache.get('foo')).toEqual('bar');
this.subject.onNext();
expect(cache.get('foo')).toEqual('bar');
this.subject.onNext();
expect(cache.get('foo')).toBeUndefined();
});
});
});

0 comments on commit a3291a6

Please sign in to comment.