identity_cache 0.4.1 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/.github/probots.yml +2 -0
- data/.github/workflows/ci.yml +92 -0
- data/.gitignore +2 -0
- data/.rubocop.yml +5 -0
- data/CAVEATS.md +25 -0
- data/CHANGELOG.md +73 -19
- data/Gemfile +5 -1
- data/LICENSE +1 -1
- data/README.md +49 -27
- data/Rakefile +14 -5
- data/dev.yml +12 -16
- data/gemfiles/Gemfile.latest-release +8 -0
- data/gemfiles/Gemfile.min-supported +7 -0
- data/gemfiles/Gemfile.rails-edge +7 -0
- data/identity_cache.gemspec +29 -10
- data/lib/identity_cache.rb +78 -51
- data/lib/identity_cache/belongs_to_caching.rb +12 -40
- data/lib/identity_cache/cache_fetcher.rb +6 -5
- data/lib/identity_cache/cache_hash.rb +2 -2
- data/lib/identity_cache/cache_invalidation.rb +4 -11
- data/lib/identity_cache/cache_key_generation.rb +17 -65
- data/lib/identity_cache/cache_key_loader.rb +128 -0
- data/lib/identity_cache/cached.rb +7 -0
- data/lib/identity_cache/cached/association.rb +87 -0
- data/lib/identity_cache/cached/attribute.rb +123 -0
- data/lib/identity_cache/cached/attribute_by_multi.rb +37 -0
- data/lib/identity_cache/cached/attribute_by_one.rb +88 -0
- data/lib/identity_cache/cached/belongs_to.rb +100 -0
- data/lib/identity_cache/cached/embedded_fetching.rb +41 -0
- data/lib/identity_cache/cached/prefetcher.rb +61 -0
- data/lib/identity_cache/cached/primary_index.rb +96 -0
- data/lib/identity_cache/cached/recursive/association.rb +109 -0
- data/lib/identity_cache/cached/recursive/has_many.rb +9 -0
- data/lib/identity_cache/cached/recursive/has_one.rb +9 -0
- data/lib/identity_cache/cached/reference/association.rb +16 -0
- data/lib/identity_cache/cached/reference/has_many.rb +105 -0
- data/lib/identity_cache/cached/reference/has_one.rb +100 -0
- data/lib/identity_cache/configuration_dsl.rb +53 -215
- data/lib/identity_cache/encoder.rb +95 -0
- data/lib/identity_cache/expiry_hook.rb +36 -0
- data/lib/identity_cache/fallback_fetcher.rb +2 -1
- data/lib/identity_cache/load_strategy/eager.rb +28 -0
- data/lib/identity_cache/load_strategy/lazy.rb +71 -0
- data/lib/identity_cache/load_strategy/load_request.rb +20 -0
- data/lib/identity_cache/load_strategy/multi_load_request.rb +27 -0
- data/lib/identity_cache/mem_cache_store_cas.rb +53 -0
- data/lib/identity_cache/memoized_cache_proxy.rb +137 -58
- data/lib/identity_cache/parent_model_expiration.rb +46 -11
- data/lib/identity_cache/query_api.rb +102 -408
- data/lib/identity_cache/railtie.rb +8 -0
- data/lib/identity_cache/record_not_found.rb +6 -0
- data/lib/identity_cache/should_use_cache.rb +1 -0
- data/lib/identity_cache/version.rb +3 -2
- data/lib/identity_cache/with_primary_index.rb +136 -0
- data/lib/identity_cache/without_primary_index.rb +24 -3
- data/performance/cache_runner.rb +25 -73
- data/performance/cpu.rb +4 -3
- data/performance/externals.rb +4 -3
- data/performance/profile.rb +6 -5
- data/railgun.yml +16 -0
- metadata +60 -73
- data/.travis.yml +0 -30
- data/Gemfile.rails42 +0 -6
- data/Gemfile.rails50 +0 -6
- data/test/attribute_cache_test.rb +0 -110
- data/test/cache_fetch_includes_test.rb +0 -46
- data/test/cache_hash_test.rb +0 -14
- data/test/cache_invalidation_test.rb +0 -139
- data/test/deeply_nested_associated_record_test.rb +0 -19
- data/test/denormalized_has_many_test.rb +0 -211
- data/test/denormalized_has_one_test.rb +0 -160
- data/test/fetch_multi_test.rb +0 -308
- data/test/fetch_test.rb +0 -258
- data/test/fixtures/serialized_record.mysql2 +0 -0
- data/test/fixtures/serialized_record.postgresql +0 -0
- data/test/helpers/active_record_objects.rb +0 -106
- data/test/helpers/database_connection.rb +0 -72
- data/test/helpers/serialization_format.rb +0 -42
- data/test/helpers/update_serialization_format.rb +0 -24
- data/test/identity_cache_test.rb +0 -29
- data/test/index_cache_test.rb +0 -161
- data/test/memoized_attributes_test.rb +0 -49
- data/test/memoized_cache_proxy_test.rb +0 -107
- data/test/normalized_belongs_to_test.rb +0 -107
- data/test/normalized_has_many_test.rb +0 -231
- data/test/normalized_has_one_test.rb +0 -9
- data/test/prefetch_associations_test.rb +0 -364
- data/test/readonly_test.rb +0 -109
- data/test/recursive_denormalized_has_many_test.rb +0 -131
- data/test/save_test.rb +0 -82
- data/test/schema_change_test.rb +0 -112
- data/test/serialization_format_change_test.rb +0 -16
- data/test/test_helper.rb +0 -140
data/test/fetch_multi_test.rb
DELETED
@@ -1,308 +0,0 @@
|
|
1
|
-
require "test_helper"
|
2
|
-
|
3
|
-
class FetchMultiTest < IdentityCache::TestCase
|
4
|
-
NAMESPACE = IdentityCache.cache_namespace
|
5
|
-
|
6
|
-
def setup
|
7
|
-
super
|
8
|
-
@bob = Item.create!(:title => 'bob')
|
9
|
-
@joe = Item.create!(:title => 'joe')
|
10
|
-
@fred = Item.create!(:title => 'fred')
|
11
|
-
@bob_blob_key = "#{NAMESPACE}blob:Item:#{cache_hash("created_at:datetime,id:integer,item_id:integer,title:string,updated_at:datetime")}:1"
|
12
|
-
@joe_blob_key = "#{NAMESPACE}blob:Item:#{cache_hash("created_at:datetime,id:integer,item_id:integer,title:string,updated_at:datetime")}:2"
|
13
|
-
@fred_blob_key = "#{NAMESPACE}blob:Item:#{cache_hash("created_at:datetime,id:integer,item_id:integer,title:string,updated_at:datetime")}:3"
|
14
|
-
@tenth_blob_key = "#{NAMESPACE}blob:Item:#{cache_hash("created_at:datetime,id:integer,item_id:integer,title:string,updated_at:datetime")}:10"
|
15
|
-
end
|
16
|
-
|
17
|
-
def test_fetch_multi_with_no_records
|
18
|
-
assert_equal [], Item.fetch_multi
|
19
|
-
end
|
20
|
-
|
21
|
-
def test_fetch_multi_namespace
|
22
|
-
Item.send(:include, SwitchNamespace)
|
23
|
-
bob_blob_key, joe_blob_key, fred_blob_key = [@bob_blob_key, @joe_blob_key, @fred_blob_key].map { |k| "ns:#{k}" }
|
24
|
-
cache_response = {}
|
25
|
-
cache_response[bob_blob_key] = cache_response_for(@bob)
|
26
|
-
cache_response[joe_blob_key] = cache_response_for(@joe)
|
27
|
-
cache_response[fred_blob_key] = cache_response_for(@fred)
|
28
|
-
IdentityCache.cache.expects(:fetch_multi).with(bob_blob_key, joe_blob_key, fred_blob_key).returns(cache_response)
|
29
|
-
assert_equal [@bob, @joe, @fred], Item.fetch_multi(@bob.id, @joe.id, @fred.id)
|
30
|
-
end
|
31
|
-
|
32
|
-
def test_fetch_multi_with_all_hits
|
33
|
-
cache_response = {}
|
34
|
-
cache_response[@bob_blob_key] = cache_response_for(@bob)
|
35
|
-
cache_response[@joe_blob_key] = cache_response_for(@joe)
|
36
|
-
cache_response[@fred_blob_key] = cache_response_for(@fred)
|
37
|
-
IdentityCache.cache.expects(:fetch_multi).with(@bob_blob_key, @joe_blob_key, @fred_blob_key).returns(cache_response)
|
38
|
-
assert_equal [@bob, @joe, @fred], Item.fetch_multi(@bob.id, @joe.id, @fred.id)
|
39
|
-
end
|
40
|
-
|
41
|
-
def test_fetch_multi_with_all_misses
|
42
|
-
cache_response = {}
|
43
|
-
cache_response[@bob_blob_key] = nil
|
44
|
-
cache_response[@joe_blob_key] = nil
|
45
|
-
cache_response[@fred_blob_key] = nil
|
46
|
-
fetch_multi = fetch_multi_stub(cache_response)
|
47
|
-
assert_equal [@bob, @joe, @fred], Item.fetch_multi(@bob.id, @joe.id, @fred.id)
|
48
|
-
assert fetch_multi.has_been_called_with?(@bob_blob_key, @joe_blob_key, @fred_blob_key)
|
49
|
-
end
|
50
|
-
|
51
|
-
def test_fetch_multi_with_mixed_hits_and_misses
|
52
|
-
cache_response = {}
|
53
|
-
cache_response[@bob_blob_key] = cache_response_for(@bob)
|
54
|
-
cache_response[@joe_blob_key] = nil
|
55
|
-
cache_response[@fred_blob_key] = cache_response_for(@fred)
|
56
|
-
fetch_multi = fetch_multi_stub(cache_response)
|
57
|
-
assert_equal [@bob, @joe, @fred], Item.fetch_multi(@bob.id, @joe.id, @fred.id)
|
58
|
-
assert fetch_multi.has_been_called_with?(@bob_blob_key, @joe_blob_key, @fred_blob_key)
|
59
|
-
end
|
60
|
-
|
61
|
-
def test_fetch_multi_with_mixed_hits_and_misses_and_responses_in_the_wrong_order
|
62
|
-
cache_response = {}
|
63
|
-
cache_response[@bob_blob_key] = nil
|
64
|
-
cache_response[@joe_blob_key] = nil
|
65
|
-
cache_response[@fred_blob_key] = cache_response_for(@fred)
|
66
|
-
fetch_multi = fetch_multi_stub(cache_response)
|
67
|
-
assert_equal [@bob, @fred, @joe], Item.fetch_multi(@bob.id, @fred.id, @joe.id)
|
68
|
-
assert fetch_multi.has_been_called_with?(@bob_blob_key, @fred_blob_key, @joe_blob_key)
|
69
|
-
end
|
70
|
-
|
71
|
-
def test_fetch_multi_with_mixed_hits_and_misses_and_non_existant_keys_1
|
72
|
-
populate_only_fred
|
73
|
-
|
74
|
-
fetch_multi = fetch_multi_stub(@cache_response)
|
75
|
-
assert_equal [@bob, @joe, @fred], Item.fetch_multi(10, @bob.id, @joe.id, @fred.id)
|
76
|
-
assert fetch_multi.has_been_called_with?(@tenth_blob_key, @bob_blob_key, @joe_blob_key, @fred_blob_key)
|
77
|
-
end
|
78
|
-
|
79
|
-
def test_fetch_multi_with_mixed_hits_and_misses_and_non_existant_keys_2
|
80
|
-
populate_only_fred
|
81
|
-
|
82
|
-
fetch_multi = fetch_multi_stub(@cache_response)
|
83
|
-
assert_equal [@fred, @bob, @joe], Item.fetch_multi(@fred.id, @bob.id, 10, @joe.id)
|
84
|
-
assert fetch_multi.has_been_called_with?(@fred_blob_key, @bob_blob_key, @tenth_blob_key, @joe_blob_key)
|
85
|
-
end
|
86
|
-
|
87
|
-
|
88
|
-
def test_fetch_multi_works_with_nils
|
89
|
-
cache_result = {1 => IdentityCache::CACHED_NIL, 2 => IdentityCache::CACHED_NIL}
|
90
|
-
fetch_result = {1 => nil, 2 => nil}
|
91
|
-
|
92
|
-
fetcher.expects(:cas_multi).with([1, 2]).twice.returns(nil, cache_result)
|
93
|
-
fetcher.expects(:add).with(1, IdentityCache::CACHED_NIL).once
|
94
|
-
fetcher.expects(:add).with(2, IdentityCache::CACHED_NIL).once
|
95
|
-
|
96
|
-
results = IdentityCache.fetch_multi(1,2) do |keys|
|
97
|
-
[nil, nil]
|
98
|
-
end
|
99
|
-
assert_equal fetch_result, results
|
100
|
-
|
101
|
-
results = IdentityCache.fetch_multi(1,2) do |keys|
|
102
|
-
flunk "Contents should have been fetched from cache successfully"
|
103
|
-
end
|
104
|
-
|
105
|
-
assert_equal fetch_result, results
|
106
|
-
end
|
107
|
-
|
108
|
-
def test_fetch_multi_works_with_blanks
|
109
|
-
cache_result = {1 => false, 2 => ' '}
|
110
|
-
|
111
|
-
fetcher.expects(:fetch_multi).with([1,2]).returns(cache_result)
|
112
|
-
|
113
|
-
results = IdentityCache.fetch_multi(1,2) do |keys|
|
114
|
-
flunk "Contents should have been fetched from cache successfully"
|
115
|
-
end
|
116
|
-
|
117
|
-
assert_equal cache_result, results
|
118
|
-
end
|
119
|
-
|
120
|
-
def test_fetch_multi_duplicate_ids
|
121
|
-
assert_equal [@joe, @bob, @joe], Item.fetch_multi(@joe.id, @bob.id, @joe.id)
|
122
|
-
end
|
123
|
-
|
124
|
-
def test_fetch_multi_with_duplicate_ids_hits_backend_once_per_id
|
125
|
-
cache_response = {
|
126
|
-
@joe_blob_key => cache_response_for(@joe),
|
127
|
-
@bob_blob_key => cache_response_for(@bob),
|
128
|
-
}
|
129
|
-
|
130
|
-
fetcher.expects(:fetch_multi).with([@joe_blob_key, @bob_blob_key]).returns(cache_response)
|
131
|
-
result = Item.fetch_multi(@joe.id, @bob.id, @joe.id)
|
132
|
-
|
133
|
-
assert_equal [@joe, @bob, @joe], result
|
134
|
-
end
|
135
|
-
|
136
|
-
def test_fetch_multi_with_open_transactions_hits_the_database
|
137
|
-
Item.connection.expects(:open_transactions).at_least_once.returns(1)
|
138
|
-
fetcher.expects(:fetch_multi).never
|
139
|
-
assert_equal [@bob, @joe, @fred], Item.fetch_multi(@bob.id, @joe.id, @fred.id)
|
140
|
-
end
|
141
|
-
|
142
|
-
def test_fetch_multi_with_open_transactions_returns_results_in_the_order_of_the_passed_ids
|
143
|
-
Item.connection.expects(:open_transactions).at_least_once.returns(1)
|
144
|
-
assert_equal [@joe, @bob, @fred], Item.fetch_multi(@joe.id, @bob.id, @fred.id)
|
145
|
-
end
|
146
|
-
|
147
|
-
def test_fetch_multi_with_open_transactions_should_compacts_returned_array
|
148
|
-
Item.connection.expects(:open_transactions).at_least_once.returns(1)
|
149
|
-
assert_equal [@joe, @fred], Item.fetch_multi(@joe.id, 0, @fred.id)
|
150
|
-
end
|
151
|
-
|
152
|
-
def test_fetch_multi_with_duplicate_ids_in_transaction_returns_results_in_the_order_of_the_passed_ids
|
153
|
-
Item.connection.expects(:open_transactions).at_least_once.returns(1)
|
154
|
-
assert_equal [@joe, @bob, @joe], Item.fetch_multi(@joe.id, @bob.id, @joe.id)
|
155
|
-
end
|
156
|
-
|
157
|
-
def test_find_batch_coerces_ids_to_primary_key_type
|
158
|
-
mock_relation = mock("ActiveRecord::Relation")
|
159
|
-
Item.expects(:where).returns(mock_relation)
|
160
|
-
mock_relation.expects(:includes).returns(stub(:to_a => [@bob, @joe, @fred]))
|
161
|
-
|
162
|
-
Item.send(:find_batch, [@bob, @joe, @fred].map(&:id).map(&:to_s))
|
163
|
-
end
|
164
|
-
|
165
|
-
def test_fetch_multi_doesnt_freeze_keys
|
166
|
-
cache_response = {}
|
167
|
-
cache_response[@bob_blob_key] = cache_response_for(@bob)
|
168
|
-
cache_response[@joe_blob_key] = cache_response_for(@fred)
|
169
|
-
|
170
|
-
IdentityCache.expects(:fetch_multi).with{ |*args| args.none?(&:frozen?) }.returns(cache_response)
|
171
|
-
|
172
|
-
Item.fetch_multi(@bob.id, @joe.id)
|
173
|
-
end
|
174
|
-
|
175
|
-
def test_fetch_multi_array
|
176
|
-
assert_equal [@joe, @bob], Item.fetch_multi([@joe.id, @bob.id])
|
177
|
-
end
|
178
|
-
|
179
|
-
def test_fetch_multi_reads_in_batches
|
180
|
-
cache_response = {}
|
181
|
-
cache_response[@bob_blob_key] = cache_response_for(@bob)
|
182
|
-
cache_response[@joe_blob_key] = cache_response_for(@joe)
|
183
|
-
|
184
|
-
with_batch_size 1 do
|
185
|
-
fetcher.expects(:fetch_multi).with([@bob_blob_key]).returns(cache_response).once
|
186
|
-
fetcher.expects(:fetch_multi).with([@joe_blob_key]).returns(cache_response).once
|
187
|
-
assert_equal [@bob, @joe], Item.fetch_multi(@bob.id, @joe.id)
|
188
|
-
end
|
189
|
-
end
|
190
|
-
|
191
|
-
def test_fetch_multi_max_stack_level
|
192
|
-
cache_response = { @fred_blob_key => cache_response_for(@fred) }
|
193
|
-
fetcher.stubs(:fetch_multi).returns(cache_response)
|
194
|
-
assert_nothing_raised { Item.fetch_multi([@fred.id] * 200_000) }
|
195
|
-
end
|
196
|
-
|
197
|
-
def test_fetch_multi_with_non_id_primary_key
|
198
|
-
fixture = KeyedRecord.create!(:value => "a") { |r| r.hashed_key = 123 }
|
199
|
-
assert_equal [fixture], KeyedRecord.fetch_multi(123, 456)
|
200
|
-
end
|
201
|
-
|
202
|
-
def test_fetch_multi_after_expiring_a_record
|
203
|
-
Item.fetch_multi(@joe.id, @fred.id)
|
204
|
-
@bob.send(:expire_cache)
|
205
|
-
assert_equal IdentityCache::DELETED, backend.read(@bob.primary_cache_index_key)
|
206
|
-
|
207
|
-
add = Spy.on(IdentityCache.cache.cache_fetcher, :add).and_call_through
|
208
|
-
|
209
|
-
assert_equal [@bob, @joe, @fred], Item.fetch_multi(@bob.id, @joe.id, @fred.id)
|
210
|
-
refute add.has_been_called?
|
211
|
-
assert_equal cache_response_for(Item.find(@bob.id)), backend.read(@bob.primary_cache_index_key)
|
212
|
-
end
|
213
|
-
|
214
|
-
def test_fetch_multi_raises_when_called_on_a_scope
|
215
|
-
assert_raises(IdentityCache::UnsupportedScopeError) do
|
216
|
-
Item.where(updated_at: nil).fetch_multi(@bob.id, @joe.id, @fred.id)
|
217
|
-
end
|
218
|
-
end
|
219
|
-
|
220
|
-
def test_fetch_multi_on_derived_model_raises
|
221
|
-
assert_raises(IdentityCache::DerivedModelError) do
|
222
|
-
StiRecordTypeA.fetch_multi(1, 2)
|
223
|
-
end
|
224
|
-
end
|
225
|
-
|
226
|
-
def test_fetch_multi_with_mixed_hits_and_misses_returns_only_readonly_records
|
227
|
-
IdentityCache.with_fetch_read_only_records do
|
228
|
-
cache_response = {}
|
229
|
-
cache_response[@bob_blob_key] = cache_response_for(@bob)
|
230
|
-
cache_response[@joe_blob_key] = nil
|
231
|
-
cache_response[@fred_blob_key] = cache_response_for(@fred)
|
232
|
-
fetch_multi = fetch_multi_stub(cache_response)
|
233
|
-
|
234
|
-
response = Item.fetch_multi(@bob.id, @joe.id, @fred.id)
|
235
|
-
assert fetch_multi.has_been_called_with?(@bob_blob_key, @joe_blob_key, @fred_blob_key)
|
236
|
-
assert_equal [@bob, @joe, @fred], response
|
237
|
-
|
238
|
-
assert response.all?(&:readonly?)
|
239
|
-
end
|
240
|
-
end
|
241
|
-
|
242
|
-
def test_fetch_multi_with_mixed_hits_and_misses_and_responses_in_the_wrong_order_returns_readonly
|
243
|
-
IdentityCache.with_fetch_read_only_records do
|
244
|
-
cache_response = {}
|
245
|
-
cache_response[@bob_blob_key] = nil
|
246
|
-
cache_response[@joe_blob_key] = nil
|
247
|
-
cache_response[@fred_blob_key] = cache_response_for(@fred)
|
248
|
-
fetch_multi = fetch_multi_stub(cache_response)
|
249
|
-
|
250
|
-
response = Item.fetch_multi(@bob.id, @joe.id, @fred.id)
|
251
|
-
assert fetch_multi.has_been_called_with?(@bob_blob_key, @joe_blob_key, @fred_blob_key)
|
252
|
-
assert_equal [@bob, @joe, @fred], response
|
253
|
-
|
254
|
-
assert response.all?(&:readonly?)
|
255
|
-
end
|
256
|
-
end
|
257
|
-
|
258
|
-
def test_fetch_multi_with_open_transactions_returns_non_readonly_records
|
259
|
-
IdentityCache.with_fetch_read_only_records do
|
260
|
-
Item.transaction do
|
261
|
-
assert_equal IdentityCache.should_use_cache?, false
|
262
|
-
IdentityCache.cache.expects(:fetch_multi).never
|
263
|
-
assert Item.fetch_multi(@bob.id, @joe.id, @fred.id).none?(&:readonly?)
|
264
|
-
end
|
265
|
-
end
|
266
|
-
end
|
267
|
-
|
268
|
-
def test_fetch_multi_with_no_keys_does_not_query_when_cache_is_disabled
|
269
|
-
Item.stubs(:should_use_cache?).returns(false)
|
270
|
-
|
271
|
-
assert_queries(0) do
|
272
|
-
assert_memcache_operations(0) do
|
273
|
-
Item.fetch_multi
|
274
|
-
end
|
275
|
-
end
|
276
|
-
end
|
277
|
-
|
278
|
-
private
|
279
|
-
|
280
|
-
def populate_only_fred
|
281
|
-
@cache_response = {}
|
282
|
-
@cache_response[@bob_blob_key] = nil
|
283
|
-
@cache_response[@joe_blob_key] = nil
|
284
|
-
@cache_response[@tenth_blob_key] = nil
|
285
|
-
@cache_response[@fred_blob_key] = cache_response_for(@fred)
|
286
|
-
end
|
287
|
-
|
288
|
-
def cache_response_for(record)
|
289
|
-
{class: record.class, attributes: record.attributes_before_type_cast}
|
290
|
-
end
|
291
|
-
|
292
|
-
def with_batch_size(size)
|
293
|
-
previous_batch_size = IdentityCache::BATCH_SIZE
|
294
|
-
IdentityCache.send(:remove_const, :BATCH_SIZE)
|
295
|
-
IdentityCache.const_set(:BATCH_SIZE, size)
|
296
|
-
yield
|
297
|
-
ensure
|
298
|
-
IdentityCache.send(:remove_const, :BATCH_SIZE)
|
299
|
-
IdentityCache.const_set(:BATCH_SIZE, previous_batch_size)
|
300
|
-
end
|
301
|
-
|
302
|
-
def fetch_multi_stub(cache_response)
|
303
|
-
Spy.on(IdentityCache.cache, :fetch_multi).and_return do |*args, &block|
|
304
|
-
nil_keys = cache_response.select {|_, v| v.nil? }.keys
|
305
|
-
cache_response.merge(Hash[nil_keys.zip(block.call(nil_keys))])
|
306
|
-
end
|
307
|
-
end
|
308
|
-
end
|
data/test/fetch_test.rb
DELETED
@@ -1,258 +0,0 @@
|
|
1
|
-
require "test_helper"
|
2
|
-
|
3
|
-
class FetchTest < IdentityCache::TestCase
|
4
|
-
NAMESPACE = IdentityCache::CacheKeyGeneration::DEFAULT_NAMESPACE
|
5
|
-
|
6
|
-
def setup
|
7
|
-
super
|
8
|
-
Item.cache_index :title, :unique => true
|
9
|
-
Item.cache_index :id, :title, :unique => true
|
10
|
-
|
11
|
-
@record = Item.new
|
12
|
-
@record.id = 1
|
13
|
-
@record.title = 'bob'
|
14
|
-
@cached_value = {class: @record.class, attributes: @record.attributes_before_type_cast}
|
15
|
-
@blob_key = "#{NAMESPACE}blob:Item:#{cache_hash("created_at:datetime,id:integer,item_id:integer,title:string,updated_at:datetime")}:1"
|
16
|
-
@index_key = "#{NAMESPACE}attr:Item:id:title:#{cache_hash('bob')}"
|
17
|
-
end
|
18
|
-
|
19
|
-
def test_fetch_with_garbage_input
|
20
|
-
assert_nil Item.fetch_by_id('garbage')
|
21
|
-
end
|
22
|
-
|
23
|
-
def test_fetch_cache_hit
|
24
|
-
IdentityCache.cache.expects(:fetch).with(@blob_key).returns(@cached_value)
|
25
|
-
|
26
|
-
assert_equal @record, Item.fetch(1)
|
27
|
-
end
|
28
|
-
|
29
|
-
def test_fetch_hit_cache_namespace
|
30
|
-
old_ns = IdentityCache.cache_namespace
|
31
|
-
IdentityCache.cache_namespace = proc { |model| "#{model.table_name}:#{old_ns}" }
|
32
|
-
|
33
|
-
new_blob_key = "items:#{@blob_key}"
|
34
|
-
IdentityCache.cache.expects(:fetch).with(new_blob_key).returns(@cached_value)
|
35
|
-
|
36
|
-
assert_equal @record, Item.fetch(1)
|
37
|
-
ensure
|
38
|
-
IdentityCache.cache_namespace = old_ns
|
39
|
-
end
|
40
|
-
|
41
|
-
def test_exists_with_identity_cache_when_cache_hit
|
42
|
-
IdentityCache.cache.expects(:fetch).with(@blob_key).returns(@cached_value)
|
43
|
-
|
44
|
-
assert Item.exists_with_identity_cache?(1)
|
45
|
-
end
|
46
|
-
|
47
|
-
def test_exists_with_identity_cache_when_cache_miss_and_in_db
|
48
|
-
fetch = Spy.on(IdentityCache.cache, :fetch).and_call_through
|
49
|
-
Item.expects(:resolve_cache_miss).with(1).once.returns(@record)
|
50
|
-
|
51
|
-
assert Item.exists_with_identity_cache?(1)
|
52
|
-
assert fetch.has_been_called_with?(@blob_key)
|
53
|
-
end
|
54
|
-
|
55
|
-
def test_exists_with_identity_cache_when_cache_miss_and_not_in_db
|
56
|
-
fetch = Spy.on(IdentityCache.cache, :fetch).and_call_through
|
57
|
-
Item.expects(:resolve_cache_miss).with(1).once.returns(nil)
|
58
|
-
|
59
|
-
assert !Item.exists_with_identity_cache?(1)
|
60
|
-
assert fetch.has_been_called_with?(@blob_key)
|
61
|
-
end
|
62
|
-
|
63
|
-
def test_fetch_miss
|
64
|
-
Item.expects(:resolve_cache_miss).with(1).once.returns(@record)
|
65
|
-
|
66
|
-
results = []
|
67
|
-
fetch = Spy.on(IdentityCache.cache, :fetch).and_return {|_, &block| block.call.tap {|result| results << result}}
|
68
|
-
|
69
|
-
assert_equal @record, Item.fetch(1)
|
70
|
-
assert fetch.has_been_called_with?(@blob_key)
|
71
|
-
assert_equal [@cached_value], results
|
72
|
-
end
|
73
|
-
|
74
|
-
def test_fetch_miss_with_non_id_primary_key
|
75
|
-
hashed_key = Zlib::crc32("foo") % (2 ** 30 - 1)
|
76
|
-
fixture = KeyedRecord.create!(:value => "foo") { |r| r.hashed_key = hashed_key }
|
77
|
-
assert_equal fixture, KeyedRecord.fetch(hashed_key)
|
78
|
-
end
|
79
|
-
|
80
|
-
def test_fetch_conflict
|
81
|
-
resolve_cache_miss = Spy.on(Item, :resolve_cache_miss).and_return do
|
82
|
-
@record.send(:expire_cache)
|
83
|
-
@record
|
84
|
-
end
|
85
|
-
add = Spy.on(fetcher, :add).and_call_through
|
86
|
-
|
87
|
-
assert_equal @record, Item.fetch(1)
|
88
|
-
assert resolve_cache_miss.has_been_called_with?(1)
|
89
|
-
assert add.has_been_called_with?(@blob_key, @cached_value)
|
90
|
-
assert_equal IdentityCache::DELETED, backend.read(@record.primary_cache_index_key)
|
91
|
-
end
|
92
|
-
|
93
|
-
def test_fetch_conflict_after_delete
|
94
|
-
@record.send(:expire_cache)
|
95
|
-
assert_equal IdentityCache::DELETED, backend.read(@record.primary_cache_index_key)
|
96
|
-
|
97
|
-
resolve_cache_miss = Spy.on(Item, :resolve_cache_miss).and_return do
|
98
|
-
@record.send(:expire_cache)
|
99
|
-
@record
|
100
|
-
end
|
101
|
-
add = Spy.on(IdentityCache.cache.cache_fetcher, :add).and_call_through
|
102
|
-
|
103
|
-
assert_equal @record, Item.fetch(1)
|
104
|
-
assert resolve_cache_miss.has_been_called_with?(1)
|
105
|
-
refute add.has_been_called?
|
106
|
-
assert_equal IdentityCache::DELETED, backend.read(@record.primary_cache_index_key)
|
107
|
-
end
|
108
|
-
|
109
|
-
def test_fetch_by_id_not_found_should_return_nil
|
110
|
-
nonexistent_record_id = 10
|
111
|
-
fetcher.expects(:add).with(@blob_key + '0', IdentityCache::CACHED_NIL)
|
112
|
-
|
113
|
-
assert_nil Item.fetch_by_id(nonexistent_record_id)
|
114
|
-
end
|
115
|
-
|
116
|
-
def test_fetch_not_found_should_raise
|
117
|
-
nonexistent_record_id = 10
|
118
|
-
fetcher.expects(:add).with(@blob_key + '0', IdentityCache::CACHED_NIL)
|
119
|
-
|
120
|
-
assert_raises(ActiveRecord::RecordNotFound) { Item.fetch(nonexistent_record_id) }
|
121
|
-
end
|
122
|
-
|
123
|
-
def test_cached_nil_expiry_on_record_creation
|
124
|
-
key = @record.primary_cache_index_key
|
125
|
-
|
126
|
-
assert_nil Item.fetch_by_id(@record.id)
|
127
|
-
assert_equal IdentityCache::CACHED_NIL, backend.read(key)
|
128
|
-
|
129
|
-
@record.save!
|
130
|
-
assert_equal IdentityCache::DELETED, backend.read(key)
|
131
|
-
end
|
132
|
-
|
133
|
-
def test_fetch_by_title_hit
|
134
|
-
values = []
|
135
|
-
fetch = Spy.on(IdentityCache.cache, :fetch).and_return do |key, &block|
|
136
|
-
case key
|
137
|
-
# Read record with title bob
|
138
|
-
when @index_key then block.call.tap {|val| values << val}
|
139
|
-
# got id, do memcache lookup on that, hit -> done
|
140
|
-
when @blob_key then @cached_value
|
141
|
-
end
|
142
|
-
end
|
143
|
-
|
144
|
-
# Id not found, use sql, SELECT id FROM records WHERE title = '...' LIMIT 1"
|
145
|
-
Item.connection.expects(:exec_query).returns(ActiveRecord::Result.new(['id'], [[1]]))
|
146
|
-
|
147
|
-
assert_equal @record, Item.fetch_by_title('bob')
|
148
|
-
assert_equal [1], values
|
149
|
-
assert fetch.has_been_called_with?(@index_key)
|
150
|
-
assert fetch.has_been_called_with?(@blob_key)
|
151
|
-
end
|
152
|
-
|
153
|
-
def test_fetch_by_title_cache_namespace
|
154
|
-
Item.send(:include, SwitchNamespace)
|
155
|
-
IdentityCache.cache.expects(:fetch).with("ns:#{@index_key}").returns(1)
|
156
|
-
IdentityCache.cache.expects(:fetch).with("ns:#{@blob_key}").returns(@cached_value)
|
157
|
-
|
158
|
-
assert_equal @record, Item.fetch_by_title('bob')
|
159
|
-
end
|
160
|
-
|
161
|
-
def test_fetch_by_title_stores_idcnil
|
162
|
-
Item.connection.expects(:exec_query).once.returns(ActiveRecord::Result.new([], []))
|
163
|
-
add = Spy.on(fetcher, :add).and_call_through
|
164
|
-
fetch = Spy.on(fetcher, :fetch).and_call_through
|
165
|
-
assert_nil Item.fetch_by_title('bob') # exec_query => nil
|
166
|
-
|
167
|
-
assert_nil Item.fetch_by_title('bob') # returns cached nil
|
168
|
-
assert_nil Item.fetch_by_title('bob') # returns cached nil
|
169
|
-
|
170
|
-
assert add.has_been_called_with?(@index_key, IdentityCache::CACHED_NIL)
|
171
|
-
assert_equal 3, fetch.calls.length
|
172
|
-
end
|
173
|
-
|
174
|
-
def test_fetch_by_bang_method
|
175
|
-
Item.connection.expects(:exec_query).returns(ActiveRecord::Result.new([], []))
|
176
|
-
assert_raises ActiveRecord::RecordNotFound do
|
177
|
-
Item.fetch_by_title!('bob')
|
178
|
-
end
|
179
|
-
end
|
180
|
-
|
181
|
-
def test_fetch_does_not_communicate_to_cache_with_nil_id
|
182
|
-
fetcher.expects(:fetch).never
|
183
|
-
fetcher.expects(:add).never
|
184
|
-
assert_raises(ActiveRecord::RecordNotFound) { Item.fetch(nil) }
|
185
|
-
end
|
186
|
-
|
187
|
-
def test_fetch_cache_hit_does_not_checkout_database_connection
|
188
|
-
@record.save!
|
189
|
-
record = Item.fetch(@record.id)
|
190
|
-
|
191
|
-
ActiveRecord::Base.clear_active_connections!
|
192
|
-
|
193
|
-
assert_equal record, Item.fetch(@record.id)
|
194
|
-
|
195
|
-
assert_equal false, ActiveRecord::Base.connection_handler.active_connections?
|
196
|
-
end
|
197
|
-
|
198
|
-
def test_fetch_raises_when_called_on_a_scope
|
199
|
-
assert_raises(IdentityCache::UnsupportedScopeError) do
|
200
|
-
Item.where(updated_at: nil).fetch(1)
|
201
|
-
end
|
202
|
-
end
|
203
|
-
|
204
|
-
def test_fetch_by_raises_when_called_on_a_scope
|
205
|
-
assert_raises(IdentityCache::UnsupportedScopeError) do
|
206
|
-
Item.where(updated_at: nil).fetch_by_id(1)
|
207
|
-
end
|
208
|
-
end
|
209
|
-
|
210
|
-
def test_fetch_on_derived_model_raises
|
211
|
-
assert_raises(IdentityCache::DerivedModelError) do
|
212
|
-
StiRecordTypeA.fetch(1)
|
213
|
-
end
|
214
|
-
end
|
215
|
-
|
216
|
-
def test_returned_records_are_readonly_on_cache_hit
|
217
|
-
IdentityCache.with_fetch_read_only_records do
|
218
|
-
IdentityCache.cache.expects(:fetch).with(@blob_key).returns(@cached_value)
|
219
|
-
assert Item.fetch(1).readonly?
|
220
|
-
end
|
221
|
-
end
|
222
|
-
|
223
|
-
def test_returned_records_are_readonly_on_cache_miss
|
224
|
-
IdentityCache.with_fetch_read_only_records do
|
225
|
-
fetch = Spy.on(IdentityCache.cache, :fetch).and_call_through
|
226
|
-
Item.expects(:resolve_cache_miss).with(1).once.returns(@record)
|
227
|
-
|
228
|
-
assert Item.exists_with_identity_cache?(1)
|
229
|
-
assert fetch.has_been_called_with?(@blob_key)
|
230
|
-
assert Item.fetch(1).readonly?
|
231
|
-
end
|
232
|
-
end
|
233
|
-
|
234
|
-
def test_returned_records_are_not_readonly_with_open_transactions
|
235
|
-
IdentityCache.with_fetch_read_only_records do
|
236
|
-
|
237
|
-
@record.transaction do
|
238
|
-
fetch = Spy.on(IdentityCache.cache, :fetch).and_call_through
|
239
|
-
Item.expects(:resolve_cache_miss).with(1).once.returns(@record)
|
240
|
-
|
241
|
-
refute IdentityCache.should_use_cache?
|
242
|
-
refute fetch.has_been_called_with?(@blob_key)
|
243
|
-
refute Item.fetch(1).readonly?, "Fetched item was read-only"
|
244
|
-
end
|
245
|
-
end
|
246
|
-
end
|
247
|
-
|
248
|
-
def test_respects_should_use_cache_on_record
|
249
|
-
@record.save
|
250
|
-
Item.stubs(:should_use_cache?).returns(false)
|
251
|
-
|
252
|
-
assert_memcache_operations(0) do
|
253
|
-
assert_queries(1) do
|
254
|
-
Item.fetch_by_id(@record.id)
|
255
|
-
end
|
256
|
-
end
|
257
|
-
end
|
258
|
-
end
|