identity_cache 0.5.1 → 1.0.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.
Files changed (91) hide show
  1. checksums.yaml +5 -5
  2. data/.github/probots.yml +2 -0
  3. data/.github/workflows/ci.yml +26 -0
  4. data/.gitignore +1 -0
  5. data/.rubocop.yml +5 -0
  6. data/.travis.yml +24 -9
  7. data/CHANGELOG.md +21 -0
  8. data/Gemfile +5 -1
  9. data/README.md +28 -26
  10. data/Rakefile +14 -5
  11. data/dev.yml +9 -16
  12. data/gemfiles/Gemfile.latest-release +6 -0
  13. data/gemfiles/Gemfile.rails-edge +6 -0
  14. data/gemfiles/Gemfile.rails52 +6 -0
  15. data/identity_cache.gemspec +26 -10
  16. data/lib/identity_cache.rb +49 -46
  17. data/lib/identity_cache/belongs_to_caching.rb +12 -40
  18. data/lib/identity_cache/cache_fetcher.rb +6 -5
  19. data/lib/identity_cache/cache_hash.rb +2 -2
  20. data/lib/identity_cache/cache_invalidation.rb +4 -11
  21. data/lib/identity_cache/cache_key_generation.rb +17 -65
  22. data/lib/identity_cache/cache_key_loader.rb +128 -0
  23. data/lib/identity_cache/cached.rb +7 -0
  24. data/lib/identity_cache/cached/association.rb +87 -0
  25. data/lib/identity_cache/cached/attribute.rb +123 -0
  26. data/lib/identity_cache/cached/attribute_by_multi.rb +37 -0
  27. data/lib/identity_cache/cached/attribute_by_one.rb +88 -0
  28. data/lib/identity_cache/cached/belongs_to.rb +93 -0
  29. data/lib/identity_cache/cached/embedded_fetching.rb +41 -0
  30. data/lib/identity_cache/cached/prefetcher.rb +51 -0
  31. data/lib/identity_cache/cached/primary_index.rb +97 -0
  32. data/lib/identity_cache/cached/recursive/association.rb +68 -0
  33. data/lib/identity_cache/cached/recursive/has_many.rb +9 -0
  34. data/lib/identity_cache/cached/recursive/has_one.rb +9 -0
  35. data/lib/identity_cache/cached/reference/association.rb +16 -0
  36. data/lib/identity_cache/cached/reference/has_many.rb +105 -0
  37. data/lib/identity_cache/cached/reference/has_one.rb +100 -0
  38. data/lib/identity_cache/configuration_dsl.rb +53 -215
  39. data/lib/identity_cache/encoder.rb +95 -0
  40. data/lib/identity_cache/expiry_hook.rb +36 -0
  41. data/lib/identity_cache/fallback_fetcher.rb +2 -1
  42. data/lib/identity_cache/load_strategy/eager.rb +28 -0
  43. data/lib/identity_cache/load_strategy/lazy.rb +71 -0
  44. data/lib/identity_cache/load_strategy/load_request.rb +20 -0
  45. data/lib/identity_cache/load_strategy/multi_load_request.rb +27 -0
  46. data/lib/identity_cache/memoized_cache_proxy.rb +127 -58
  47. data/lib/identity_cache/parent_model_expiration.rb +45 -11
  48. data/lib/identity_cache/query_api.rb +128 -394
  49. data/lib/identity_cache/railtie.rb +8 -0
  50. data/lib/identity_cache/record_not_found.rb +6 -0
  51. data/lib/identity_cache/should_use_cache.rb +1 -0
  52. data/lib/identity_cache/version.rb +3 -2
  53. data/lib/identity_cache/with_primary_index.rb +136 -0
  54. data/lib/identity_cache/without_primary_index.rb +24 -3
  55. data/performance/cache_runner.rb +28 -34
  56. data/performance/cpu.rb +3 -2
  57. data/performance/externals.rb +4 -3
  58. data/performance/profile.rb +6 -5
  59. data/railgun.yml +16 -0
  60. metadata +44 -73
  61. data/Gemfile.rails42 +0 -6
  62. data/Gemfile.rails50 +0 -6
  63. data/test/attribute_cache_test.rb +0 -110
  64. data/test/cache_fetch_includes_test.rb +0 -46
  65. data/test/cache_hash_test.rb +0 -14
  66. data/test/cache_invalidation_test.rb +0 -139
  67. data/test/deeply_nested_associated_record_test.rb +0 -19
  68. data/test/denormalized_has_many_test.rb +0 -214
  69. data/test/denormalized_has_one_test.rb +0 -160
  70. data/test/fetch_multi_test.rb +0 -308
  71. data/test/fetch_test.rb +0 -258
  72. data/test/fixtures/serialized_record.mysql2 +0 -0
  73. data/test/fixtures/serialized_record.postgresql +0 -0
  74. data/test/helpers/active_record_objects.rb +0 -106
  75. data/test/helpers/database_connection.rb +0 -72
  76. data/test/helpers/serialization_format.rb +0 -51
  77. data/test/helpers/update_serialization_format.rb +0 -27
  78. data/test/identity_cache_test.rb +0 -29
  79. data/test/index_cache_test.rb +0 -161
  80. data/test/memoized_attributes_test.rb +0 -59
  81. data/test/memoized_cache_proxy_test.rb +0 -107
  82. data/test/normalized_belongs_to_test.rb +0 -107
  83. data/test/normalized_has_many_test.rb +0 -231
  84. data/test/normalized_has_one_test.rb +0 -9
  85. data/test/prefetch_associations_test.rb +0 -379
  86. data/test/readonly_test.rb +0 -109
  87. data/test/recursive_denormalized_has_many_test.rb +0 -131
  88. data/test/save_test.rb +0 -82
  89. data/test/schema_change_test.rb +0 -112
  90. data/test/serialization_format_change_test.rb +0 -16
  91. data/test/test_helper.rb +0 -140
@@ -1,160 +0,0 @@
1
- require "test_helper"
2
-
3
- class DenormalizedHasOneTest < IdentityCache::TestCase
4
- def setup
5
- super
6
- PolymorphicRecord.include(IdentityCache::WithoutPrimaryIndex)
7
- Item.cache_has_one :associated
8
- Item.cache_index :title, :unique => true
9
- @record = Item.new(:title => 'foo')
10
- @record.associated = AssociatedRecord.new(:name => 'bar')
11
- @record.save
12
-
13
- @record.reload
14
- end
15
-
16
- def test_on_cache_miss_record_should_embed_associated_object
17
- fetch = Spy.on(IdentityCache.cache, :fetch).and_call_through
18
-
19
- record_from_cache_miss = Item.fetch_by_title('foo')
20
-
21
- assert_equal @record, record_from_cache_miss
22
- assert_not_nil @record.fetch_associated
23
- assert_equal @record.associated, record_from_cache_miss.fetch_associated
24
- assert fetch.has_been_called_with?(@record.attribute_cache_key_for_attribute_and_current_values(:id, [:title], true))
25
- assert fetch.has_been_called_with?(@record.primary_cache_index_key)
26
- end
27
-
28
- def test_on_cache_miss_record_should_embed_nil_object
29
-
30
- @record.associated = nil
31
- @record.save!
32
- @record.reload
33
- Item.expects(:resolve_cache_miss).with(@record.id).once.returns(@record)
34
-
35
- fetch = Spy.on(IdentityCache.cache, :fetch).and_call_through
36
-
37
- record_from_cache_miss = Item.fetch_by_title('foo')
38
- record_from_cache_miss.expects(:associated).never
39
-
40
- assert_equal @record, record_from_cache_miss
41
- 5.times do
42
- assert_nil record_from_cache_miss.fetch_associated
43
- end
44
- assert fetch.has_been_called_with?(@record.attribute_cache_key_for_attribute_and_current_values(:id, [:title], true))
45
- assert fetch.has_been_called_with?(@record.primary_cache_index_key)
46
- end
47
-
48
- def test_on_record_from_the_db_will_use_normal_association
49
- record_from_db = Item.find_by_title('foo')
50
-
51
- assert_equal @record, record_from_db
52
- assert_not_nil record_from_db.fetch_associated
53
- end
54
-
55
- def test_on_cache_hit_record_should_come_back_with_cached_association
56
- Item.expects(:resolve_cache_miss).with(1).once.returns(@record)
57
- Item.fetch_by_title('foo')
58
-
59
- record_from_cache_hit = Item.fetch_by_title('foo')
60
- expected = @record.associated
61
-
62
- assert_equal @record, record_from_cache_hit
63
- assert_equal expected, record_from_cache_hit.fetch_associated
64
- end
65
-
66
- def test_on_cache_hit_record_should_come_back_with_cached_nil_association
67
- @record.associated = nil
68
- @record.save!
69
- @record.reload
70
-
71
- Item.expects(:resolve_cache_miss).with(1).once.returns(@record)
72
- Item.fetch_by_title('foo')
73
-
74
- record_from_cache_hit = Item.fetch_by_title('foo')
75
- record_from_cache_hit.expects(:associated).never
76
-
77
- assert_equal @record, record_from_cache_hit
78
- 5.times do
79
- assert_nil record_from_cache_hit.fetch_associated
80
- end
81
- end
82
-
83
- def test_changes_in_associated_record_should_expire_the_parents_cache
84
- Item.fetch_by_title('foo')
85
- key = @record.primary_cache_index_key
86
- assert_not_nil IdentityCache.cache.fetch(key)
87
-
88
- IdentityCache.cache.expects(:delete).at_least(1).with(key)
89
- IdentityCache.cache.expects(:delete).with(@record.associated.primary_cache_index_key)
90
-
91
- @record.associated.save
92
- end
93
-
94
- def test_cached_associations_after_commit_hook_will_not_fail_on_undefined_parent_association
95
- ar = AssociatedRecord.new
96
- ar.save
97
- assert_nothing_raised { ar.expire_parent_caches }
98
- end
99
-
100
- def test_cache_without_guessable_inverse_name_raises
101
- assert_raises IdentityCache::InverseAssociationError do
102
- Item.cache_has_one :polymorphic_record, :embed => true
103
- end
104
- end
105
-
106
- def test_cache_without_guessable_inverse_name_does_not_raise_when_inverse_name_specified
107
- assert_nothing_raised do
108
- Item.cache_has_one :polymorphic_record, :inverse_name => :owner, :embed => true
109
- end
110
- end
111
-
112
- def test_unsupported_through_assocation
113
- assert_raises IdentityCache::UnsupportedAssociationError, "caching through associations isn't supported" do
114
- Item.has_one :deeply_associated, :through => :associated, :class_name => 'DeeplyAssociatedRecord'
115
- Item.cache_has_one :deeply_associated, :embed => true
116
- end
117
- end
118
-
119
- def test_cache_has_one_on_derived_model_raises
120
- assert_raises(IdentityCache::DerivedModelError) do
121
- StiRecordTypeA.cache_has_one :polymorphic_record, :inverse_name => :owner, :embed => true
122
- end
123
- end
124
-
125
- def test_returned_record_should_be_readonly_on_cache_hit
126
- IdentityCache.with_fetch_read_only_records do
127
- Item.fetch_by_title('foo')
128
- record_from_cache_hit = Item.fetch_by_title('foo')
129
- assert record_from_cache_hit.fetch_associated.readonly?
130
- refute record_from_cache_hit.associated.readonly?
131
- end
132
- end
133
-
134
- def test_returned_record_should_be_readonly_on_cache_miss
135
- IdentityCache.with_fetch_read_only_records do
136
- assert IdentityCache.should_use_cache?
137
- record_from_cache_miss = Item.fetch_by_title('foo')
138
- assert record_from_cache_miss.fetch_associated.readonly?
139
- end
140
- end
141
-
142
- def test_db_returned_record_should_never_be_readonly
143
- IdentityCache.with_fetch_read_only_records do
144
- record_from_db = Item.find_by_title('foo')
145
- uncached_record = record_from_db.associated
146
- refute uncached_record.readonly?
147
- record_from_db.fetch_associated
148
- refute uncached_record.readonly?
149
- end
150
- end
151
-
152
- def test_returned_record_with_open_transactions_should_not_be_readonly
153
- IdentityCache.with_fetch_read_only_records do
154
- Item.transaction do
155
- refute IdentityCache.should_use_cache?
156
- refute Item.fetch_by_title('foo').fetch_associated.readonly?
157
- end
158
- end
159
- end
160
- end
@@ -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.name, 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