-
Notifications
You must be signed in to change notification settings - Fork 5
Guide to Caching
Building a responsive and interactive site requires a large amounts of caching and cache manipulation techniques to create a usable product. Without caching, site responsiveness would be poor, and without cache manipulation, the behavior of the site would appear buggy and broken.
There are two types of caches in Commonplace. The first is called the general cache or the request cache. The general cache is a basic key-value store where keys are strings and values are any data type. The general cache is used for, as its name implies, general purpose caching of processed or unprocessed blobs of information.
The second type of cache is called the model cache. The model is a more flexible data store, which organizes objects in a key-value way where the key is a predefined type (generally strings, but could be numbers) and the value is a JavaScript object. The model cache can store many types (or "models") of data and is used to store discrete objects rather than lists or blobs.
The general cache is managed in the cache
module. It provides the following interface:
Returns a boolean describing whether or not the string key
exists in the cache.
Returns the value associated with the string key
in the cache. If the key is not in the cache, this function returns undefined.
Sets the value value
to the string key
as the key in the cache.
If key
is matched by a rewriter defined in the rewriters
module, the value will be manipulated.
Removes all entries from the cache. If filter
is truthy, it will be called with each cache entry's key as the first argument. If the return value of filter
is truthy, the key will be preserved in the cache.
Deletes the entry with the the string key
as the key from the cache.
Attempts to rewrite the cache. For each entry in the cache, matcher
will be called with the entry's key as the first argument. If matcher
returns a truthy value, worker
will be called with the entry's value as the first argument and the entry's key as the second argument. The return value of worker
will replace the entry's value in the cache.
If limit
is truthy, it is expected to be a positive integer. After matcher
has returned limit
truthy values, attemptRewrite
will return.
If a matched entry's key exists in the persistent cache, the persistent cache entry's value will be replaced with the value returned by worker
.
A JavaScript object containing the cache.
This object contains a nearly identical interface to the cache
module itself, with the following exceptions:
- There is no
cache.persist.persist
,cache.persist.raw
, orcache.persist.attemptRewrite
. - Values set with
cache.persist.set()
will be persisted after the session has ended (using thestorage
module). - Entries created via
cache.persist
will be saved to the "normal" cache as well.cache.persist.set(x, y)
is the same ascache.persist.set(x, y); cache.set(x, y);
. - Entries modified with non-
cache.persist
methods will not update the persistent cache, with the exception ofcache.attemptRewrite()
. I.e.:cache.persist.set("foo", "bar"); cache.set("foo", "zap"); alert(cache.persist.get("foo"));
will alert "zap" instead of "bar", but the persistent cache (upon refresh) will still contain "bar". -
cache.persist.purge()
does not accept afilter
argument.
The rewriters
module returns an array, which can contain functions in the following format:
function(new_key, new_value, raw) {
// ...
}
For each key set with cache.set()
and cache.persist.set()
, each rewriter is called with the new entry's key as the first argument (new_key
), the new entry's value as the second argument (new_value
), and a copy of the whole cache as the third argument (raw
; equivalent to cache.raw
).
If a rewriter method returns undefined
, it will have no effect.
If a rewriter method returns null
, the entry will not be created in the general cache and no other rewriters will be called. If the entry was created via cache.persist.set()
, it will still be created in the persistent cache.
If a rewriter method returns a truthy value, the truthy return value will be used in lieu of the value passed to cache.set()
. If a new value is returned, any remaining rewriters will still be called and will be called with the updated value.
The usefulness of cache rewriters is to update existing information in the cache instead of simply adding more information. For instance, if data is returned from an API in a paginated way, it's sometimes more useful to store all results in a single cache entry rather than in multiple entries. To accomplish this, a rewriter could detect when cache.set()
is called with page N of a results list and update page 1 instead (and return null
to prevent the new page from being saved).
A savvy rewriter method could also be used to detect when an outdated cache entry is being created and reject it, or update the entry's value with new information before it is set.
The requests
module will store all data it receives from GET requests in the general cache.
The model cache is exposed via the models
module. The models module itself returns a function which accepts one argument: the name of the model you wish to modify. The following boilerplate module definition is not uncommon:
define('my_module', ['models'], function(models) {
var app_model = models('app');
var foo_model = models('foo');
// ...
});
The models that are available need to be defined in the JavaScript object model_prototypes
in the settings
module. This object should look like {"model_name": "key"}
where "key"
is the name of the key in model objects to do primary lookups by (the lookup
method, documented below).
The interface of each model (returned by models("model_name")
) has the following interface:
The object object
will be indexed and stored in the model cache. The model object automatically looks up the object's primary key (defined in settings.model_prototypes
) and stores it accordingly.
If object
is an object, it will be treated as an entry in the model cache. Its primary key will be extracted, and the equivalent of model.lookup(object[primary_key])
will be returned.
If object
is an array of objects, the function will return the equivalent of object.map(model.uncast)
(though the function will not recurse further).
This method exists to update existing caches.
Returns a promise object that produces the model cache entry for key
. If the entry associated with key
is not in the cache, a new "getter" will be created to supply the promise with data form a URL (url
). By default, requests.get
will be used, though an alternative getter can be supplied through the getter
argument.
Purges the model cache of all entries.
Deletes the model cache entry with the primary key key
. If by
is specified, the entry will be matched by the field by
instead of the primary key. For instance, models('person').del('Basta', 'last_name')
will delete the first model cache entry where entry.last_name === 'Basta'
.
Returns the model cache entry with the primary key key
. If by
is specified, the entry will be matched by the field by
instead of the primary key (a la model.del
).
The builder uses the model cache in conjunction with the as
and key
parameters, where as
lists a model name and key
lists the primary key of the model object. Specifically, the builder uses the .get()
method to return either an AJAX request to fetch the object or a promise object which simulates a cached response containing the value stored in the model cache.
The builder also "uncasts" each object it encounters when as
and pluck
are used together on a cached response. This updates objects in the general cache with freshly updated information from the model cache. Consider the following scenario:
Builder requests API:
- Request 1
- Object A
- Object B
- Object C
The builder pushes each object to the model cache:
- "Object" Model
- A: Object A
- B: Object B
- C: Object C
The builder requests another API which contains updated objects. The new
request is stored in the general cache and the model cache is updated. The
cache will appear as follows:
- Request 2
- Updated Object A
- Updated Object B
- Updated Object C
- Request 1
- Object A
- Object B
- Object C
- "Object" Model
- A: Updated Object A
- B: Updated Object B
- C: Updated Object C
If "page 1" is requested again, the builder will fetch Request 1 from the
general cache. But the general cache's copy of Request 1 contains outdated
versions of the objects.
The builder will, instead of using the old values, pass them to `uncast`, which
will return the updated values. The builder will also update the entry for
Request 1 in the general cache with the new objects to allow the old objects to
be released and garbage collected:
- Request 2
- Updated Object A
- Updated Object B
- Updated Object C
- Request 1
- Updated Object A
- Updated Object B
- Updated Object C