-
-
Notifications
You must be signed in to change notification settings - Fork 87
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This PR introduces a new helper method to support fragment caching. I’ve been reluctant to add fragment caching because it’s difficult to expire the cache when templates change and I didn’t want to try to build up a static dependency tree between components, partials, etc. For now, we’re keeping it simple by expiring the cache on each deploy via a deploy key (see below). **Example:** ```ruby cache @products do @products.each do |product| cache product do h1 { product.name } end end end ``` The `cache` method will take user cache keys and combine them with built-in supplemental keys to cache the captured content against. `cache` will call `cache_store`, which must return a Phlex cache store. ## Supplemental keys The `cache` method supplements your cache keys with the following: 1. `Phlex::DEPLOY_KEY` — the time that the app was booted and Phlex was loaded. 5. The name of the class where the caching is taking place. This prevents collisions between classes. 6. The name of the method where the caching is taking place. This prevents collisions between different methods in the same class. 7. The line number where the `cache` method is called. This prevents collisions between different lines, especially when no custom cache keys are provided. ## Low level caching The `low_level_cache` method requires a cache key from you and you control the entire cache key. ## Cache store interface Cache stores are objects that respond to `fetch(key, &content)`. This method must return the result of `yield` or a previously cached result of `yield`. This method may raise if the result of `yield` is not a string or if the key is not valid. It’s up to you what keys are valid, but note that Phlex itself uses arrays, string and integers in its keys. This interface is a subset of Rails’ cache interface, meaning Rails cache interface implements this interface can can be used as a Phlex cache store. ## `FIFOCacheStore` This PR also introduces a new cache store based on the `Phlex::FIFO`. This is an extremely fast in-memory cache store that evicts keys on a first-in-first-out basis. It can be initialised with a max bytesize.
- Loading branch information
1 parent
a19b1e8
commit 1b9de03
Showing
6 changed files
with
159 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
# frozen_string_literal: true | ||
|
||
# An extremely fast in-memory cache store that evicts keys on a first-in-first-out basis. | ||
class Phlex::FIFOCacheStore | ||
def initialize(max_bytesize: 2 ** 20) | ||
@fifo = Phlex::FIFO.new( | ||
max_bytesize:, | ||
max_value_bytesize: max_bytesize | ||
) | ||
end | ||
|
||
def fetch(key) | ||
fifo = @fifo | ||
key = map_key(key) | ||
|
||
if (result = fifo[key]) | ||
result | ||
else | ||
result = yield | ||
|
||
case result | ||
when String | ||
fifo[key] = result | ||
else | ||
raise ArgumentError.new("Invalid cache value: #{result.class}") | ||
end | ||
|
||
result | ||
end | ||
end | ||
|
||
private | ||
|
||
def map_key(value) | ||
case value | ||
when Array | ||
value.map { |it| map_key(it) } | ||
when Hash | ||
value.to_h { |k, v| [map_key(k), map_key(v)].freeze } | ||
when String, Symbol, Integer, Float, Time, true, false, nil | ||
value | ||
else | ||
if value.respond_to?(:cache_key) | ||
map_key(value.cache_key) | ||
else | ||
raise ArgumentError.new("Invalid cache key: #{value.class}") | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
# frozen_string_literal: true | ||
|
||
module Phlex::NullCacheStore | ||
extend self | ||
|
||
def fetch(key) | ||
yield | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
# frozen_string_literal: true | ||
|
||
test "fetch caches the yield" do | ||
store = Phlex::FIFOCacheStore.new | ||
count = 0 | ||
|
||
first_read = store.fetch("a") do | ||
count += 1 | ||
"A" | ||
end | ||
|
||
assert_equal first_read, "A" | ||
assert_equal count, 1 | ||
|
||
second_read = store.fetch("a") do | ||
failure! { "This block should not have been called." } | ||
"B" | ||
end | ||
|
||
assert_equal second_read, "A" | ||
assert_equal count, 1 | ||
end | ||
|
||
test "nested caches do not lead to contention" do | ||
store = Phlex::FIFOCacheStore.new | ||
|
||
result = store.fetch("a") do | ||
[ | ||
"A", | ||
store.fetch("b") { "B" }, | ||
].join(", ") | ||
end | ||
|
||
assert_equal result, "A, B" | ||
end | ||
|
||
test "caching something other than a string" do | ||
store = Phlex::FIFOCacheStore.new | ||
|
||
assert_raises(ArgumentError) do | ||
store.fetch("a") { 1 } | ||
end | ||
end |