From b9ce9eeb1d8fb18c5f2a2dcfe3c8511b5eb6ec8e Mon Sep 17 00:00:00 2001 From: imjoshdean Date: Mon, 26 Sep 2016 15:34:28 -0500 Subject: [PATCH 1/6] Add attribute for Maps and Lists when using fall-through-cache and can/map --- fall-through-cache/fall-through-cache.js | 65 +++++++++++++- fall-through-cache/fall-through-cache_test.js | 88 +++++++++++++++++++ 2 files changed, 151 insertions(+), 2 deletions(-) diff --git a/fall-through-cache/fall-through-cache.js b/fall-through-cache/fall-through-cache.js index 36ad2130..325c62ee 100644 --- a/fall-through-cache/fall-through-cache.js +++ b/fall-through-cache/fall-through-cache.js @@ -72,9 +72,65 @@ 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, { + _setIsConsistent: function(base, connection) { + return function(isConsistent) { + setExpando(this, "_isConsistent", isConsistent); + canEvent.dispatch.call(this, "_isConsistent", [isConsistent, !isConsistent]); + }; + }, + isConsistent: function(base, connection) { + return function() { + Observation.add(this,"_isConsistent"); + return !!getExpando(this, "_isConsistent"); + }; + } + }); + }; var behavior = { + init: function() { + this.List && addIsConsistent(this, this.List); + this.Map && addIsConsistent(this, this.Map); + }, + _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 @@ -147,17 +203,19 @@ 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.cacheConnection.updateListData(listData, set); self.updatedList(list, listData, set); + self._setIsConsistent(list, true) self.deleteListReference(list, set); }, function(e){ @@ -254,11 +312,14 @@ 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.cacheConnection.updateData(instanceData2); self.updatedInstance(instance, instanceData2); + self._setIsConsistent(instance, true); self.deleteInstanceReference(instance); }, function(e){ diff --git a/fall-through-cache/fall-through-cache_test.js b/fall-through-cache/fall-through-cache_test.js index 5d813d02..0318b319 100644 --- a/fall-through-cache/fall-through-cache_test.js +++ b/fall-through-cache/fall-through-cache_test.js @@ -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; @@ -229,3 +233,87 @@ 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"); + }); +}); \ No newline at end of file From 00adf7eb651607d455da8dcd14fdab633854c039 Mon Sep 17 00:00:00 2001 From: imjoshdean Date: Mon, 26 Sep 2016 15:50:28 -0500 Subject: [PATCH 2/6] Fix linting issues. --- fall-through-cache/fall-through-cache.js | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/fall-through-cache/fall-through-cache.js b/fall-through-cache/fall-through-cache.js index 325c62ee..3badca5d 100644 --- a/fall-through-cache/fall-through-cache.js +++ b/fall-through-cache/fall-through-cache.js @@ -123,8 +123,12 @@ module.exports = connect.behavior("fall-through-cache",function(baseConnect){ var behavior = { init: function() { - this.List && addIsConsistent(this, this.List); - this.Map && addIsConsistent(this, this.Map); + if(this.List) { + addIsConsistent(this, this.List); + } + if(this.Map) { + addIsConsistent(this, this.Map); + } }, _setIsConsistent: function(instance, isConsistent) { if(instance._setIsConsistent) { @@ -212,10 +216,10 @@ module.exports = connect.behavior("fall-through-cache",function(baseConnect){ setTimeout(function(){ baseConnect.getListData.call(self, set).then(function(listData){ -; + self.cacheConnection.updateListData(listData, set); self.updatedList(list, listData, set); - self._setIsConsistent(list, true) + self._setIsConsistent(list, true); self.deleteListReference(list, set); }, function(e){ @@ -312,7 +316,7 @@ 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) + self._setIsConsistent(instance, false); setTimeout(function(){ baseConnect.getData.call(self, params).then(function(instanceData2){ From cc6f54018173c70ae82f19b19d17f5d1e0d385cf Mon Sep 17 00:00:00 2001 From: imjoshdean Date: Mon, 26 Sep 2016 16:58:12 -0500 Subject: [PATCH 3/6] Call base connection's init. --- fall-through-cache/fall-through-cache.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fall-through-cache/fall-through-cache.js b/fall-through-cache/fall-through-cache.js index 3badca5d..2a6a77da 100644 --- a/fall-through-cache/fall-through-cache.js +++ b/fall-through-cache/fall-through-cache.js @@ -129,6 +129,8 @@ module.exports = connect.behavior("fall-through-cache",function(baseConnect){ if(this.Map) { addIsConsistent(this, this.Map); } + + baseConnect.init.apply(this, arguments); }, _setIsConsistent: function(instance, isConsistent) { if(instance._setIsConsistent) { From 921bb4b254fb77246a653d2b9747cd232feb0fe4 Mon Sep 17 00:00:00 2001 From: imjoshdean Date: Tue, 27 Sep 2016 12:26:51 -0500 Subject: [PATCH 4/6] Add inconsistencyReason attribute to maps and lists. --- fall-through-cache/fall-through-cache.js | 61 +++++++++++++++++-- fall-through-cache/fall-through-cache_test.js | 58 ++++++++++++++++++ 2 files changed, 115 insertions(+), 4 deletions(-) diff --git a/fall-through-cache/fall-through-cache.js b/fall-through-cache/fall-through-cache.js index 2a6a77da..830c3f06 100644 --- a/fall-through-cache/fall-through-cache.js +++ b/fall-through-cache/fall-through-cache.js @@ -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.can-map can/map Mixins * * A fall through cache that checks another `cacheConnection`. * @@ -106,23 +107,62 @@ module.exports = connect.behavior("fall-through-cache",function(baseConnect){ 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.can-map + * + * Returns whether or not the data is consistent between the server and + * the fall-through-cache data. + * + * @signature `map.isConsistent()` or `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.can-map + * + * Returns the error of the AJAX call from the base data layer. + * + * @signature `map.inconsistencyReason` or `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. if(this.List) { addIsConsistent(this, this.List); } @@ -219,14 +259,15 @@ module.exports = connect.behavior("fall-through-cache",function(baseConnect){ 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); }); },1); }); @@ -323,14 +364,16 @@ module.exports = connect.behavior("fall-through-cache",function(baseConnect){ 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); }); @@ -344,6 +387,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); + } } }; diff --git a/fall-through-cache/fall-through-cache_test.js b/fall-through-cache/fall-through-cache_test.js index 0318b319..779e4e8a 100644 --- a/fall-through-cache/fall-through-cache_test.js +++ b/fall-through-cache/fall-through-cache_test.js @@ -316,4 +316,62 @@ asyncTest("isConsistent works on data immediately from cache for get (#144)", 2, 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({}); + }); }); \ No newline at end of file From 87d4a8bea3b9efa186160a54b77dad191f856234 Mon Sep 17 00:00:00 2001 From: imjoshdean Date: Tue, 27 Sep 2016 13:03:32 -0500 Subject: [PATCH 5/6] Fix documentation issues --- fall-through-cache/fall-through-cache.js | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/fall-through-cache/fall-through-cache.js b/fall-through-cache/fall-through-cache.js index 830c3f06..e6e63e55 100644 --- a/fall-through-cache/fall-through-cache.js +++ b/fall-through-cache/fall-through-cache.js @@ -3,7 +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.can-map can/map Mixins + * @group can-connect/fall-through-cache/fall-through-cache.mixins can/map Mixins * * A fall through cache that checks another `cacheConnection`. * @@ -123,18 +123,19 @@ module.exports = connect.behavior("fall-through-cache",function(baseConnect){ 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.can-map + * @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.isConsistent()` or `list.isConsistent()` + * @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) { @@ -143,17 +144,18 @@ module.exports = connect.behavior("fall-through-cache",function(baseConnect){ return !!getExpando(this, "_isConsistent"); }; } - /* + /** * @function can-connect/fall-through-cache/fall-through-cache.inconsistencyReason inconsistencyReason - * @parent can-connect/fall-through-cache/fall-through-cache.can-map + * @parent can-connect/fall-through-cache/fall-through-cache.mixins * * Returns the error of the AJAX call from the base data layer. * - * @signature `map.inconsistencyReason` or `list.inconsistencyReason` + * @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} */ }); From 900d49329370294ce1ac8d7c60fa7f0760e76583 Mon Sep 17 00:00:00 2001 From: imjoshdean Date: Tue, 27 Sep 2016 13:06:09 -0500 Subject: [PATCH 6/6] Forgot group fix --- fall-through-cache/fall-through-cache.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fall-through-cache/fall-through-cache.js b/fall-through-cache/fall-through-cache.js index e6e63e55..a44bca74 100644 --- a/fall-through-cache/fall-through-cache.js +++ b/fall-through-cache/fall-through-cache.js @@ -3,7 +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 can/map Mixins + * @group can-connect/fall-through-cache/fall-through-cache.mixins mixins Mixins * * A fall through cache that checks another `cacheConnection`. *