Skip to content

Commit db6c538

Browse files
committed
Implemented cache! to cache expensive data
This is going to be optimized later to use a fetch multi algorithm and strings instead of marshalling. We aren't doing any marshalling here but cache implementations always do that, like active support cache stores. By using strings instead of objects we can achieve higher throughput. The expected interface of the cache store is this: cache_store.fetch(key, options, &block) As you can guess, this is the interface implemented by active support, so it should be plug n play for rails. For other cache interfaces, you can implement a driver that have this interface and maps to your desired interface.
1 parent c56e399 commit db6c538

File tree

4 files changed

+159
-1
lines changed

4 files changed

+159
-1
lines changed

lib/abstract_builder.rb

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
1+
require 'abstract_builder/null_cache'
2+
13
class AbstractBuilder
24
@@format_key = nil
35
@@ignore_value = nil
6+
@@cache_store = NullCache.new
7+
8+
def self.cache_store!(cache_store)
9+
@@cache_store = cache_store
10+
end
411

512
def self.format_key!(&block)
613
@@format_key = block
@@ -11,9 +18,10 @@ def self.ignore_value!(&block)
1118
end
1219

1320
def initialize
14-
@stack = []
1521
@format_key = @@format_key
1622
@ignore_value = @@ignore_value
23+
@cache_store = @@cache_store
24+
@stack = []
1725
end
1826

1927
def format_key!(&block)
@@ -24,6 +32,10 @@ def ignore_value!(&block)
2432
@ignore_value = block
2533
end
2634

35+
def cache_store!(cache_store)
36+
@cache_store = cache_store
37+
end
38+
2739
def set!(key, value)
2840
@stack << [:set, key, value]
2941
end
@@ -58,6 +70,16 @@ def array!(key, collection, &block)
5870
set! key, values
5971
end
6072

73+
def cache!(key, options = nil, &block)
74+
value = @cache_store.fetch([:abstract_builder, :v1, *key], options) do
75+
builder = _inherit
76+
block.call(builder)
77+
builder.data!
78+
end
79+
80+
merge! value
81+
end
82+
6183
def data!
6284
data = {}
6385

@@ -112,6 +134,7 @@ def _inherit
112134
builder = self.class.new
113135
builder.format_key!(&@format_key)
114136
builder.ignore_value!(&@ignore_value)
137+
builder.cache_store!(@cache_store)
115138
builder
116139
end
117140

lib/abstract_builder/null_cache.rb

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
class AbstractBuilder
2+
class NullCache
3+
def fetch(key, _options = nil, &block)
4+
block.call
5+
end
6+
end
7+
end

spec/abstract_builder_spec.rb

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,46 @@
4444
end
4545
end
4646

47+
describe "#cache_store!" do
48+
let :cache_store do
49+
AbstractBuilder::NullCache.new
50+
end
51+
52+
it "caches using the given cache store" do
53+
subject.cache_store! cache_store
54+
55+
expect(cache_store).to receive(:fetch).with([:abstract_builder, :v1, :cache_key], nil).and_call_original
56+
57+
subject.cache! :cache_key do |builder|
58+
builder.cache "hit"
59+
end
60+
end
61+
62+
it "caches using the given options" do
63+
subject.cache_store! cache_store
64+
65+
expect(cache_store).to receive(:fetch).with([:abstract_builder, :v1, :cache_key], option: true).and_call_original
66+
67+
subject.cache! :cache_key, option: true do |builder|
68+
builder.cache "hit"
69+
end
70+
end
71+
72+
it "inherits the global ignore value by default" do
73+
begin
74+
AbstractBuilder.cache_store! cache_store
75+
76+
expect(cache_store).to receive(:fetch).with([:abstract_builder, :v1, :cache_key], nil).and_call_original
77+
78+
subject.cache! :cache_key do |builder|
79+
builder.cache "hit"
80+
end
81+
ensure
82+
AbstractBuilder.cache_store! AbstractBuilder::NullCache.new
83+
end
84+
end
85+
end
86+
4787
describe "#set!" do
4888
it "sets the key and value" do
4989
subject.set! :key, "value"
@@ -177,6 +217,68 @@
177217
)
178218
end
179219
end
220+
221+
context "using cache store" do
222+
it "inherits the parent cache store" do
223+
subject.cache_store! NaiveCache.new
224+
225+
subject.cache! :outside_cache_key do |builder|
226+
builder.outside_cache "hit"
227+
end
228+
229+
subject.cache! :outside_cache_key do |builder|
230+
builder.outside_cache "miss"
231+
end
232+
233+
subject.block! :meta do |meta|
234+
meta.cache! :inside_cache_key do |builder|
235+
builder.inside_cache "hit"
236+
end
237+
238+
meta.cache! :inside_cache_key do |builder|
239+
builder.inside_cache "miss"
240+
end
241+
end
242+
243+
expect(subject.data!).to eq(
244+
outside_cache: "hit",
245+
meta: {
246+
inside_cache: "hit"
247+
}
248+
)
249+
end
250+
251+
it "do not leaks the ignore value to the parent" do
252+
subject.cache_store! AbstractBuilder::NullCache.new
253+
254+
subject.cache! :outside_cache_key do |builder|
255+
builder.outside_cache "hit"
256+
end
257+
258+
subject.cache! :outside_cache_key do |builder|
259+
builder.outside_cache "miss"
260+
end
261+
262+
subject.block! :meta do |meta|
263+
meta.cache_store! NaiveCache.new
264+
265+
meta.cache! :inside_cache_key do |builder|
266+
builder.inside_cache "hit"
267+
end
268+
269+
meta.cache! :inside_cache_key do |builder|
270+
builder.inside_cache "miss"
271+
end
272+
end
273+
274+
expect(subject.data!).to eq(
275+
outside_cache: "miss",
276+
meta: {
277+
inside_cache: "hit"
278+
}
279+
)
280+
end
281+
end
180282
end
181283

182284
describe "#array!" do
@@ -199,6 +301,22 @@
199301
end
200302
end
201303

304+
describe "#cache!" do
305+
it "caches the given block" do
306+
subject.cache_store! NaiveCache.new
307+
308+
subject.cache! :cache_key do |cache|
309+
cache.cache "miss"
310+
end
311+
312+
subject.cache! :cache_key do |cache|
313+
cache.cache "hit"
314+
end
315+
316+
expect(subject.data!).to eq(cache: "miss")
317+
end
318+
end
319+
202320
describe "#method_missing" do
203321
it "sets the key and value" do
204322
subject.key "value"

spec/spec_helper.rb

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,13 @@ def died
3434
"July 17, 1967"
3535
end
3636
end
37+
38+
class NaiveCache
39+
def initialize
40+
@cache = {}
41+
end
42+
43+
def fetch(key, _options = nil, &block)
44+
@cache[key] ||= block.call
45+
end
46+
end

0 commit comments

Comments
 (0)