Module: Card::View::Cache
- Includes:
- CacheAction, Stub
- Included in:
- Card::View
- Defined in:
- card/lib/card/view/cache.rb,
card/lib/card/view/cache/stub.rb,
card/lib/card/view/cache/cache_action.rb
Overview
View::Cache supports smart card view caching.
The basic idea is that when view caching is turned on (via config.view_cache
),
we try to cache a view whenever it’s “safe” to do so. We will include everything
inside that view (including other views) until we find something that isn’t safe.
When something isn’t safe, we render a stub: a placeholder
with all the info we need to come back and replace it with the correct content
later. In this way it is possible to have many levels of cached views within
cached views.
Here are some things that we never consider safe to cache:
- a view explicitly configured never to be cached
- a view of a card with view-relevant permission restrictions
- a view other than the requested view (eg a denial view)
- a card with unsaved content changes
We also consider it unsafe to cache a view of one card within a view of a different card, so nests are always handled with a stub.
Cache configuration
Cache settings (#5) can be configured in the view definition and (less commonly) as a view option.
By far, the most common explicit caching configuration is :never
. This setting
is used to prevent over-caching, which becomes problematic when data changes
do not clear the cache.
Generally speaking, a card is smart about clearing its own view caches when anything about the card itself. So when I update the card “Johnny”, all the cached views of “Johnny” are cleared. Similarly, changes to structure rules and other basic patterns are typically well managed by the caching system.
However, there are many other potential changes that views cannot detect. Views that
are susceptible to these “cache hazards” should be configured with cache: :never
.
Cache hazards
If a view contains any of the following cache hazards, it would be wise to consider
a cache: :never
configuration:
- dynamic searches (eg
Card.search
) whose results may change - live timestamps (eg
Time.now
) - environmental variables (eg
Env.params
) - any variables altered in one view and used in another (eg
@myvar
) - other cards’ properties (eg
Card["random"].content
)
What all of the above have in common is that they involve changes about which the view caching system is unaware. This means that whether the cache hazard is rendered directly in a view or just used in its logic, it can change in a way that should change the view but won’t change the view if it’s cached.
Altering cached views
Whereas ignoring cache hazards may cause over-caching, altering cached views may cause outright errors. If a view directly alters a rendered view, it may be dangerous to cache.
# obviously safe to cache
view(:x) { "ABC" }
# also safe, because x is NOT altered
view(:y) { render_x + "DEF" }
# unsafe and thus never cached, because x is altered
view(:z, cache: :never) { render_x.reverse }
Specifically, the danger is that the inner view will be rendered as a stub, and the out view will end up altering the stub and not the view.
Although alterations should be considered dangerous, they are actually only problematic in situations where the inner view might sometimes render a stub. If the outer view is rendering a view of the same card with all the same view settings (perms, unknown, etc), there will be no stub and thus no error. Remember, however, that a view on a narrow set may inherit view settings from a general set. To be confident that a view alteration is safe, all inherited settings must be taken into account.
Caching Best Practices
Here are some good rules of thumb to make good use of view caching:
-
Use nests. If you can show the content of a different card with a nest rather than by showing the content directly, the caching system will be much happier with you.
view :bad_idea, cache: :never do Card["random"].content end view :good_idea do nest :random, view: :core end
-
Isolate the cache hazards. Consider the following variants:
view :bad_idea, cache: :never do if morning_for_user? expensive_good_morning else expensive_good_afternoon end end view :good_idea, cache: :never do morning_for_user? ? render_good_morning : render_good_afternoon end
In the first example, we have to generate expensive greetings every time we render the view. In the second, only the test is not cached.
-
If you must alter view results, consider generating the view content in a separate method.
# First Attempt view :hash_it_in do { cool: false } end view :bad_idea, cache: :never do render_badhash.merge sucks: true end #Second Attempt view :hash_it_out do hash_it_out end def hash_it_out { cool: true } end view :good_idea do hash_it_out.merge rocks: true end
The first attempt will work fine with caching off but is risky with caching on. The second is safe with caching on.
Optimizing with :always
It is never strictly necessary to use cache: :always
, but this setting can help
optimize your use of the caching system in some cases.
Consider the following views:
view(:hat) { "hat" } # ...but imagine this is computationally expensive
view(:old_hat) { "old #{render_hat}" }
view(:new_hat) { "new #{render_hat}" }
view(:red_hat) { "red #{render_hat}" }
view(:blue_hat) { "blue #{render_hat}" }
Whether “hat” uses :standard
or :always
, the hat varieties (old, new, etc…)
will fully contain the rendered hat view in their cache. However, with :standard
,
the other views will each re-render hat without attempting to cache it separately
or to find it in the cache. This could lead to man expensive renderings of the
“hat” view. By contrast, if the cache setting is :always
, then hat will be
cached and retrieved even when it’s rendered inside another cached view.
Defined Under Namespace
Modules: CacheAction, ClassMethods, Stub
Constant Summary collapse
- EXPIRE_VALUES =
%i[minute hour day week month].freeze