Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes #133, creates API for Maps and Lists for consistent data checking #154

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
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
132 changes: 127 additions & 5 deletions fall-through-cache/fall-through-cache.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
* @parent can-connect.behaviors
* @group can-connect/fall-through-cache/fall-through-cache.data Data Callbacks
* @group can-connect/fall-through-cache/fall-through-cache.hydrators Hydrators
* @group can-connect/fall-through-cache/fall-through-cache.mixins mixins Mixins
*
* A fall through cache that checks another `cacheConnection`.
*
Expand Down Expand Up @@ -72,9 +73,112 @@
var connect = require("can-connect");
var sortedSetJSON = require("../helpers/sorted-set-json");

var canEvent = require("can-event");
var Observation = require("can-observation");

module.exports = connect.behavior("fall-through-cache",function(baseConnect){
var setExpando = function(map, prop, value) {
if("attr" in map) {
map[prop] = value;
} else {
map._data[prop] = value;
}
};
var getExpando = function(map, prop) {
if("attr" in map) {
return map[prop];
} else {
return map._data[prop];
}
};

var overwrite = function( connection, Constructor, prototype, statics) {
var prop;

for(prop in prototype) {
Constructor.prototype[prop] = prototype[prop](Constructor.prototype[prop], connection);
}
if(statics) {
for(prop in statics) {
Constructor[prop] = statics[prop](Constructor[prop], connection);
}
}
};

var addIsConsistent = function(connection, Constructor) {
overwrite(connection, Constructor, {

_setInconsistencyReason: function(base, connection) {
return function(error) {
var oldError = getExpando(this, "inconsistencyReason");

setExpando(this, "inconsistencyReason", error);
canEvent.dispatch.call(this, "inconsistencyReason", [error, oldError]);
}
},

_setIsConsistent: function(base, connection) {
return function(isConsistent) {
setExpando(this, "_isConsistent", isConsistent);
canEvent.dispatch.call(this, "_isConsistent", [isConsistent, !isConsistent]);
};
},
/**
* @function can-connect/fall-through-cache/fall-through-cache.isConsistent isConsistent
* @parent can-connect/fall-through-cache/fall-through-cache.mixins
*
* Returns whether or not the data is consistent between the server and
* the fall-through-cache data.
*
* @signature `[map|list].isConsistent()`
*
* Returns true if the data has been successfully returned from the
* server and in sync with the fall-through-cache. Returns false if the
* data is from the cache.
*
* @return {Boolean}
*/
isConsistent: function(base, connection) {
return function() {
Observation.add(this,"_isConsistent");
return !!getExpando(this, "_isConsistent");
};
}
/**
* @function can-connect/fall-through-cache/fall-through-cache.inconsistencyReason inconsistencyReason
* @parent can-connect/fall-through-cache/fall-through-cache.mixins
*
* Returns the error of the AJAX call from the base data layer.
*
* @signature `[map|list].inconsistencyReason`
*
* Returns the error fo the base data layer's AJAX call if it's promise
* was rejected. If there isn't an inconsistency issue between the server
* and fall-through-cache layer, this will be undefined.
*
* @return {Object}
*/
});
};

var behavior = {
init: function() {
// If List and Map are on the behavior, then we go ahead and add the
// isConsistent API information.
Copy link
Contributor

Choose a reason for hiding this comment

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

We should probably avoid having fall-through-cache know anything about can-connect/can/map. Can you think of a way to avoid this?

Copy link
Author

Choose a reason for hiding this comment

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

Off the top of my head no. Based on the suggested requirements, these features would be specific to maps and lists, and made sense to me that if we wanted to have these function features available in templates.

Do you have any suggestions?

if(this.List) {
addIsConsistent(this, this.List);
}
if(this.Map) {
addIsConsistent(this, this.Map);
}

baseConnect.init.apply(this, arguments);
},
_setIsConsistent: function(instance, isConsistent) {
if(instance._setIsConsistent) {
instance._setIsConsistent(isConsistent);
}
},
/**
* @function can-connect/fall-through-cache/fall-through-cache.hydrateList hydrateList
* @parent can-connect/fall-through-cache/fall-through-cache.hydrators
Expand Down Expand Up @@ -147,22 +251,25 @@ module.exports = connect.behavior("fall-through-cache",function(baseConnect){
set = set || {};
var self = this;
return this.cacheConnection.getListData(set).then(function(data){

// get the list that is going to be made
// it might be possible that this never gets called, but not right now
self._getHydrateList(set, function(list){

self._setIsConsistent(list, false);
self.addListReference(list, set);

setTimeout(function(){
baseConnect.getListData.call(self, set).then(function(listData){

self._setInconsistencyReason(list, undefined);
self.cacheConnection.updateListData(listData, set);
self.updatedList(list, listData, set);
self._setIsConsistent(list, true);
self.deleteListReference(list, set);

}, function(e){
// what do we do here? self.rejectedUpdatedList ?
console.log("REJECTED", e);
console.error("baseConnect.getListData rejected", e);
self.rejectedUpdatedInstance(list, e);
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this should be rejectedUpdatedList?

Copy link
Author

Choose a reason for hiding this comment

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

I don't, "instance" was the best neutral name I could come up with to apply to both a map and a list.

});
},1);
});
Expand Down Expand Up @@ -254,16 +361,21 @@ module.exports = connect.behavior("fall-through-cache",function(baseConnect){
self._getMakeInstance(self.id(instanceData) || self.id(params), function(instance){
self.addInstanceReference(instance);

self._setIsConsistent(instance, false);

setTimeout(function(){
baseConnect.getData.call(self, params).then(function(instanceData2){


self._setInconsistencyReason(instance, undefined);
self.cacheConnection.updateData(instanceData2);
self.updatedInstance(instance, instanceData2);
self._setIsConsistent(instance, true);
self.deleteInstanceReference(instance);

}, function(e){
// what do we do here? self.rejectedUpdatedList ?
console.log("REJECTED", e);
console.error("baseConnect.getData rejected", e);
self.rejectedUpdatedInstance(instance, e);
});
},1);
});
Expand All @@ -277,6 +389,16 @@ module.exports = connect.behavior("fall-through-cache",function(baseConnect){

return listData;
});
},
rejectedUpdatedInstance: function(instance, error) {
this._setIsConsistent(instance, false);
this._setInconsistencyReason(instance, error);
},

_setInconsistencyReason: function(instance, error) {
if(instance._setInconsistencyReason) {
instance._setInconsistencyReason(error);
}
}

};
Expand Down
146 changes: 146 additions & 0 deletions fall-through-cache/fall-through-cache_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ var constructor = require("can-connect/constructor/constructor");
var fallThroughCache = require("can-connect/fall-through-cache/fall-through-cache");
var constructorStore = require("can-connect/constructor/store/store");
var dataCallbacks = require("can-connect/data/callbacks/");
var Map = require("can-map");
var List = require("can-list");
var compute = require("can-compute");
var canMap = require("can-connect/can/map/map");

var getId = function(d){
return d.id;
Expand Down Expand Up @@ -229,3 +233,145 @@ asyncTest("metadata transfered through fall through cache (#125)", function(){
});

});

asyncTest("isFromCache works on data immediately from cache for get and getList (#144)", 2, function() {
var cacheConnection = {
getListData: function() {
return testHelpers.asyncResolve({
data: [{
id: 1
}]
});
},
updateListData: function() { }
};

var getDataBehavior = function(base, options){
return {
getListData: function(){
return testHelpers.asyncResolve({
data: [{
id: 1
}]
});
}
};
};

var connection = connect([getDataBehavior, constructor, canMap, fallThroughCache, constructorStore, dataCallbacks], {
cacheConnection: cacheConnection,
Map: Map,
List: List
});

connection.getList({}).then(function(list) {
var isConsistent = compute(function() {
return list.isConsistent();
});

isConsistent.bind("change", function(ev, newVal, oldVal){
QUnit.ok(newVal, "Data is conistent and not from cache");
QUnit.start();
});

QUnit.notOk(isConsistent(), "Data is from cache and not consistent");
});
});

asyncTest("isConsistent works on data immediately from cache for get (#144)", 2, function() {
var cacheConnection = {
getData: function() {
return testHelpers.asyncResolve({
id: 1
});
},
updateData: function() { }
};

var getDataBehavior = function(base, options){
return {
getData: function() {
return testHelpers.asyncResolve({
id: 1
});
}
};
};

var connection = connect([getDataBehavior, constructor, canMap, fallThroughCache, constructorStore, dataCallbacks], {
cacheConnection: cacheConnection,
Map: Map,
List: List
});

connection.get({}).then(function(data) {
var isConsistent = compute(function() {
return data.isConsistent();
});

isConsistent.bind("change", function(ev, newVal, oldVal){
QUnit.ok(newVal, "Data is conistent and not from cache");
QUnit.start();
});

QUnit.notOk(isConsistent(), "Data is from cache and not consistent");
});
});



asyncTest("inconsistencyReason properly set if the data layer errors out (#144)", function() {
var errorReason = "Rejected the promise.";
var state = testHelpers.makeStateChecker(QUnit, ["reject-first", "resolve-second"]);
var cacheConnection = {
getData: function() {
return testHelpers.asyncResolve({
id: 1
});
},
updateData: function() { }
};

var getDataBehavior = function(base, options){
return {
getData: function() {
if(state.get() === "reject-first") {
return testHelpers.asyncReject(errorReason);
} else {
return testHelpers.asyncResolve({
id: 1
});
}

}
};
};

var connection = connect([getDataBehavior, constructor, canMap, fallThroughCache, constructorStore, dataCallbacks], {
cacheConnection: cacheConnection,
Map: Map,
List: List
});

connection.get({}).then(function(data) {
data.bind("inconsistencyReason", function(ev, newVal) {
if(state.get() === "reject-first") {
state.next();

QUnit.notOk(data.isConsistent(), "data is no longer conistent with server");
QUnit.equal(newVal, errorReason, "inconsistencyReason is the error data");

}
else {
setTimeout(function() {
QUnit.ok(data.isConsistent(), "data is conistent with server");
QUnit.equal(newVal, undefined, "inconsistencyReason no longer set");
QUnit.start();
}, 30);

}

});
connection.get({});
});
});