record-cache 0.1.2 → 0.1.3

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 (52) hide show
  1. checksums.yaml +15 -0
  2. data/lib/record_cache.rb +2 -1
  3. data/lib/record_cache/base.rb +63 -22
  4. data/lib/record_cache/datastore/active_record.rb +5 -3
  5. data/lib/record_cache/datastore/active_record_30.rb +95 -38
  6. data/lib/record_cache/datastore/active_record_31.rb +157 -54
  7. data/lib/record_cache/datastore/active_record_32.rb +444 -0
  8. data/lib/record_cache/dispatcher.rb +47 -47
  9. data/lib/record_cache/multi_read.rb +14 -1
  10. data/lib/record_cache/query.rb +36 -25
  11. data/lib/record_cache/statistics.rb +5 -5
  12. data/lib/record_cache/strategy/base.rb +49 -19
  13. data/lib/record_cache/strategy/full_table_cache.rb +81 -0
  14. data/lib/record_cache/strategy/index_cache.rb +38 -36
  15. data/lib/record_cache/strategy/unique_index_cache.rb +130 -0
  16. data/lib/record_cache/strategy/util.rb +12 -12
  17. data/lib/record_cache/test/resettable_version_store.rb +2 -9
  18. data/lib/record_cache/version.rb +1 -1
  19. data/lib/record_cache/version_store.rb +23 -16
  20. data/spec/db/schema.rb +12 -0
  21. data/spec/db/seeds.rb +10 -0
  22. data/spec/lib/active_record/visitor_spec.rb +22 -0
  23. data/spec/lib/base_spec.rb +21 -0
  24. data/spec/lib/dispatcher_spec.rb +24 -46
  25. data/spec/lib/multi_read_spec.rb +6 -6
  26. data/spec/lib/query_spec.rb +43 -43
  27. data/spec/lib/statistics_spec.rb +28 -28
  28. data/spec/lib/strategy/base_spec.rb +98 -87
  29. data/spec/lib/strategy/full_table_cache_spec.rb +68 -0
  30. data/spec/lib/strategy/index_cache_spec.rb +112 -69
  31. data/spec/lib/strategy/query_cache_spec.rb +83 -0
  32. data/spec/lib/strategy/unique_index_on_id_cache_spec.rb +317 -0
  33. data/spec/lib/strategy/unique_index_on_string_cache_spec.rb +168 -0
  34. data/spec/lib/strategy/util_spec.rb +67 -49
  35. data/spec/lib/version_store_spec.rb +22 -41
  36. data/spec/models/address.rb +9 -0
  37. data/spec/models/apple.rb +1 -1
  38. data/spec/models/banana.rb +21 -2
  39. data/spec/models/language.rb +5 -0
  40. data/spec/models/person.rb +1 -1
  41. data/spec/models/store.rb +2 -1
  42. data/spec/spec_helper.rb +7 -4
  43. data/spec/support/after_commit.rb +2 -0
  44. data/spec/support/matchers/hit_cache_matcher.rb +10 -6
  45. data/spec/support/matchers/log.rb +45 -0
  46. data/spec/support/matchers/miss_cache_matcher.rb +10 -6
  47. data/spec/support/matchers/use_cache_matcher.rb +10 -6
  48. metadata +156 -161
  49. data/lib/record_cache/strategy/id_cache.rb +0 -93
  50. data/lib/record_cache/strategy/request_cache.rb +0 -49
  51. data/spec/lib/strategy/id_cache_spec.rb +0 -168
  52. data/spec/lib/strategy/request_cache_spec.rb +0 -85
@@ -0,0 +1,317 @@
1
+ require 'spec_helper'
2
+
3
+ describe RecordCache::Strategy::UniqueIndexCache do
4
+
5
+ it "should retrieve an Apple from the cache" do
6
+ expect{ Apple.find(1) }.to miss_cache(Apple).on(:id).times(1)
7
+ expect{ Apple.find(1) }.to hit_cache(Apple).on(:id).times(1)
8
+ end
9
+
10
+ it "should accept find_by_sql queries (can not use the cache though)" do
11
+ apple2 = Apple.find(2) # prefill cache
12
+ apples = []
13
+ expect{ apples = Apple.find_by_sql("select * from apples where id = 2") }.to_not use_cache(Apple).on(:id)
14
+ expect(apples).to eq([apple2])
15
+ end
16
+
17
+ it "should accept parameterized find_by_sql queries (can not use the cache though)" do
18
+ apple1 = Apple.find(1) # prefill cache
19
+ apples = []
20
+ expect{ apples = Apple.find_by_sql(["select * from apples where id = ?", 1]) }.to_not use_cache(Apple).on(:id)
21
+ expect(apples).to eq([apple1])
22
+ end
23
+
24
+ it "should retrieve cloned records" do
25
+ @apple_1a = Apple.find(1)
26
+ @apple_1b = Apple.find(1)
27
+ expect(@apple_1a).to eq(@apple_1b)
28
+ expect(@apple_1a.object_id).to_not eq(@apple_1b.object_id)
29
+ end
30
+
31
+ context "logging" do
32
+ before(:each) do
33
+ Apple.find(1)
34
+ end
35
+
36
+ it "should write full hits to the debug log" do
37
+ expect{ Apple.find(1) }.to log(:debug, %(UniqueIndexCache on 'Apple.id' hit for ids 1))
38
+ end
39
+
40
+ it "should write full miss to the debug log" do
41
+ expect{ Apple.find(2) }.to log(:debug, %(UniqueIndexCache on 'Apple.id' miss for ids 2))
42
+ end
43
+
44
+ it "should write partial hits to the debug log" do
45
+ expect{ Apple.where(:id => [1,2]).all }.to log(:debug, %(UniqueIndexCache on 'Apple.id' partial hit for ids [1, 2]: missing [2]))
46
+ end
47
+ end
48
+
49
+ context "cacheable?" do
50
+ before(:each) do
51
+ # fill cache
52
+ @apple1 = Apple.find(1)
53
+ @apple2 = Apple.find(2)
54
+ end
55
+
56
+ # @see https://github.com/orslumen/record-cache/issues/2
57
+ it "should not use the cache when a lock is used" do
58
+ expect{ Apple.lock("any_lock").where(:id => 1).all }.to_not hit_cache(Apple)
59
+ end
60
+
61
+ it "should use the cache when a single id is requested" do
62
+ expect{ Apple.where(:id => 1).all }.to hit_cache(Apple).on(:id).times(1)
63
+ end
64
+
65
+ it "should use the cache when a multiple ids are requested" do
66
+ expect{ Apple.where(:id => [1, 2]).all }.to hit_cache(Apple).on(:id).times(2)
67
+ end
68
+
69
+ it "should use the cache when a single id is requested and the limit is 1" do
70
+ expect{ Apple.where(:id => 1).limit(1).all }.to hit_cache(Apple).on(:id).times(1)
71
+ end
72
+
73
+ it "should not use the cache when a single id is requested and the limit is > 1" do
74
+ expect{ Apple.where(:id => 1).limit(2).all }.to_not use_cache(Apple).on(:id)
75
+ end
76
+
77
+ it "should not use the cache when multiple ids are requested and the limit is 1" do
78
+ expect{ Apple.where(:id => [1, 2]).limit(1).all }.to_not use_cache(Apple).on(:id)
79
+ end
80
+
81
+ it "should use the cache when a single id is requested together with other where clauses" do
82
+ expect{ Apple.where(:id => 1).where(:name => "Adams Apple x").all }.to hit_cache(Apple).on(:id).times(1)
83
+ end
84
+
85
+ it "should use the cache when a multiple ids are requested together with other where clauses" do
86
+ expect{ Apple.where(:id => [1,2]).where(:name => "Adams Apple x").all }.to hit_cache(Apple).on(:id).times(2)
87
+ end
88
+
89
+ it "should use the cache when a single id is requested together with (simple) sort clauses" do
90
+ expect{ Apple.where(:id => 1).order("name ASC").all }.to hit_cache(Apple).on(:id).times(1)
91
+ end
92
+
93
+ it "should use the cache when a multiple ids are requested together with (simple) sort clauses" do
94
+ expect{ Apple.where(:id => [1,2]).order("name ASC").all }.to hit_cache(Apple).on(:id).times(2)
95
+ end
96
+
97
+ it "should not use the cache when a join clause is used" do
98
+ expect{ Apple.where(:id => [1,2]).joins(:store).all }.to_not use_cache(Apple).on(:id)
99
+ end
100
+ end
101
+
102
+ context "record_change" do
103
+ before(:each) do
104
+ # fill cache
105
+ @apple1 = Apple.find(1)
106
+ @apple2 = Apple.find(2)
107
+ end
108
+
109
+ it "should invalidate destroyed records" do
110
+ expect{ Apple.where(:id => 1).all }.to hit_cache(Apple).on(:id).times(1)
111
+ @apple1.destroy
112
+ expect{ @apples = Apple.where(:id => 1).all }.to miss_cache(Apple).on(:id).times(1)
113
+ expect(@apples).to be_empty
114
+ # try again, to make sure the "missing record" is not cached
115
+ expect{ Apple.where(:id => 1).all }.to miss_cache(Apple).on(:id).times(1)
116
+ end
117
+
118
+ it "should add updated records directly to the cache" do
119
+ @apple1.name = "Applejuice"
120
+ @apple1.save!
121
+ expect{ @apple = Apple.find(1) }.to hit_cache(Apple).on(:id).times(1)
122
+ expect(@apple.name).to eq("Applejuice")
123
+ end
124
+
125
+ it "should add created records directly to the cache" do
126
+ @new_apple = Apple.create!(:name => "Fresh Apple", :store_id => 1)
127
+ expect{ @apple = Apple.find(@new_apple.id) }.to hit_cache(Apple).on(:id).times(1)
128
+ expect(@apple.name).to eq("Fresh Apple")
129
+ end
130
+
131
+ it "should add updated records to the cache, also when multiple ids are queried" do
132
+ @apple1.name = "Applejuice"
133
+ @apple1.save!
134
+ expect{ @apples = Apple.where(:id => [1, 2]).order('id ASC').all }.to hit_cache(Apple).on(:id).times(2)
135
+ expect(@apples.map(&:name)).to eq(["Applejuice", "Adams Apple 2"])
136
+ end
137
+
138
+ end
139
+
140
+ context "invalidate" do
141
+ before(:each) do
142
+ @apple1 = Apple.find(1)
143
+ @apple2 = Apple.find(2)
144
+ end
145
+
146
+ it "should invalidate single records" do
147
+ Apple.record_cache[:id].invalidate(1)
148
+ expect{ Apple.find(1) }.to miss_cache(Apple).on(:id).times(1)
149
+ end
150
+
151
+ it "should only miss the cache for the invalidated record when multiple ids are queried" do
152
+ # miss on 1
153
+ Apple.record_cache[:id].invalidate(1)
154
+ expect{ Apple.where(:id => [1, 2]).all }.to miss_cache(Apple).on(:id).times(1)
155
+ # hit on 2
156
+ Apple.record_cache[:id].invalidate(1)
157
+ expect{ Apple.where(:id => [1, 2]).all }.to hit_cache(Apple).on(:id).times(1)
158
+ # nothing invalidated, both hit
159
+ expect{ Apple.where(:id => [1, 2]).all }.to hit_cache(Apple).on(:id).times(2)
160
+ end
161
+
162
+ it "should invalidate records when using update_all" do
163
+ Apple.where(:id => [3,4,5]).all # fill id cache on all Adam Store apples
164
+ expect{ @apples = Apple.where(:id => [1, 2, 3, 4, 5]).order('id ASC').all }.to hit_cache(Apple).on(:id).times(5)
165
+ expect(@apples.map(&:name)).to eq(["Adams Apple 1", "Adams Apple 2", "Adams Apple 3", "Adams Apple 4", "Adams Apple 5"])
166
+ # update 3 of the 5 apples in the Adam Store
167
+ Apple.where(:id => [1,2,3]).update_all(:name => "Uniform Apple")
168
+ expect{ @apples = Apple.where(:id => [1, 2, 3, 4, 5]).order('id ASC').all }.to hit_cache(Apple).on(:id).times(2)
169
+ expect(@apples.map(&:name)).to eq(["Uniform Apple", "Uniform Apple", "Uniform Apple", "Adams Apple 4", "Adams Apple 5"])
170
+ end
171
+
172
+ it "should invalidate reflection indexes when a has_many relation is updated" do
173
+ # assign different apples to store 2
174
+ expect{ Apple.where(:store_id => 1).all }.to hit_cache(Apple).on(:id).times(2)
175
+ store2_apple_ids = Apple.where(:store_id => 2).map(&:id)
176
+ store1 = Store.find(1)
177
+ store1.apple_ids = store2_apple_ids
178
+ store1.save!
179
+ # the apples that used to belong to store 2 are now in store 1 (incremental update)
180
+ expect{ @apple1 = Apple.find(store2_apple_ids.first) }.to hit_cache(Apple).on(:id).times(1)
181
+ expect(@apple1.store_id).to eq(1)
182
+ # the apples that used to belong to store 1 are now homeless (cache invalidated)
183
+ expect{ @homeless_apple = Apple.find(1) }.to miss_cache(Apple).on(:id).times(1)
184
+ expect(@homeless_apple.store_id).to be_nil
185
+ end
186
+
187
+ it "should reload from the DB after invalidation" do
188
+ @apple = Apple.last
189
+ Apple.record_cache.invalidate(@apple.id)
190
+ expect{ Apple.find(@apple.id) }.to miss_cache(Apple).on(:id).times(1)
191
+ end
192
+
193
+ end
194
+
195
+ context "transactions" do
196
+
197
+ it "should update the cache once the transaction is committed" do
198
+ apple1 = Apple.find(1)
199
+ ActiveRecord::Base.transaction do
200
+ apple1.name = "Committed Apple"
201
+ apple1.save!
202
+
203
+ # do not use the cache within a transaction
204
+ expect{ apple1 = Apple.find(1) }.to_not use_cache(Apple).on(:id)
205
+ expect(apple1.name).to eq("Committed Apple")
206
+ end
207
+
208
+ # use the cache again once the transaction is over
209
+ expect{ apple1 = Apple.find(1) }.to use_cache(Apple).on(:id)
210
+ expect(apple1.name).to eq("Committed Apple")
211
+ end
212
+
213
+ it "should not update the cache when the transaction is rolled back" do
214
+ apple1 = Apple.find(1)
215
+ ActiveRecord::Base.transaction do
216
+ apple1.name = "Rollback Apple"
217
+ apple1.save!
218
+
219
+ # test to make sure appl1 is not retrieved 1:1 from the cache
220
+ apple1.name = "Not saved apple"
221
+
222
+ # do not use the cache within a transaction
223
+ expect{ apple1 = Apple.find(1) }.to_not use_cache(Apple).on(:id)
224
+ expect(apple1.name).to eq("Rollback Apple")
225
+
226
+ raise ActiveRecord::Rollback, "oops"
227
+ end
228
+
229
+ # use the cache again once the transaction is over
230
+ expect{ apple1 = Apple.find(1) }.to use_cache(Apple).on(:id)
231
+ expect(apple1.name).to eq("Adams Apple 1")
232
+ end
233
+
234
+ end
235
+
236
+ context "nested transactions" do
237
+
238
+ it "should update the cache in case both transactions are committed" do
239
+ apple1, apple2 = nil
240
+
241
+ ActiveRecord::Base.transaction do
242
+ apple1 = Apple.find(1)
243
+ apple1.name = "Committed Apple 1"
244
+ apple1.save!
245
+
246
+ ActiveRecord::Base.transaction(requires_new: true) do
247
+ apple2 = Apple.find(2)
248
+ apple2.name = "Committed Apple 2"
249
+ apple2.save!
250
+ end
251
+ end
252
+
253
+ expect{ apple1 = Apple.find(1) }.to use_cache(Apple).on(:id)
254
+ expect(apple1.name).to eq("Committed Apple 1")
255
+
256
+ expect{ apple2 = Apple.find(2) }.to use_cache(Apple).on(:id)
257
+ expect(apple2.name).to eq("Committed Apple 2")
258
+ end
259
+
260
+ [:implicitly, :explicitly].each do |inner_rollback_explicit_or_implicit|
261
+ it "should not update the cache in case both transactions are #{inner_rollback_explicit_or_implicit} rolled back" do
262
+ pending "nested transaction support" if ActiveRecord::VERSION::MAJOR == 3 && ActiveRecord::VERSION::MINOR == 0
263
+ apple1, apple2 = nil
264
+
265
+ ActiveRecord::Base.transaction do
266
+ apple1 = Apple.find(1)
267
+ apple1.name = "Rollback Apple 1"
268
+ apple1.save!
269
+ apple1.name = "Saved Apple 1"
270
+
271
+ ActiveRecord::Base.transaction(requires_new: true) do
272
+ apple2 = Apple.find(2)
273
+ apple2.name = "Rollback Apple 2"
274
+ apple2.save!
275
+ apple1.name = "Saved Apple 2"
276
+
277
+ raise ActiveRecord::Rollback, "oops" if inner_rollback_explicit_or_implicit == :explicitly
278
+ end
279
+
280
+ raise ActiveRecord::Rollback, "oops"
281
+ end
282
+
283
+ expect{ apple1 = Apple.find(1) }.to use_cache(Apple).on(:id)
284
+ expect(apple1.name).to eq("Adams Apple 1")
285
+
286
+ expect{ apple2 = Apple.find(2) }.to use_cache(Apple).on(:id)
287
+ expect(apple2.name).to eq("Adams Apple 2")
288
+ end
289
+ end
290
+
291
+ it "should not update the cache for the rolled back inner transaction" do
292
+ pending "rails calls after_commit on records that are in a transaction that is rolled back"
293
+
294
+ apple1, apple2 = nil
295
+
296
+ ActiveRecord::Base.transaction do
297
+ apple1 = Apple.find(1)
298
+ apple1.name = "Committed Apple 1"
299
+ apple1.save!
300
+
301
+ ActiveRecord::Base.transaction(requires_new: true) do
302
+ apple2 = Apple.find(2)
303
+ apple2.name = "Rollback Apple 2"
304
+ apple2.save!
305
+
306
+ raise ActiveRecord::Rollback, "oops"
307
+ end
308
+ end
309
+
310
+ expect{ apple1 = Apple.find(1) }.to use_cache(Apple).on(:id)
311
+ expect(apple1.name).to eq("Committed Apple 1")
312
+
313
+ expect{ apple2 = Apple.find(2) }.to use_cache(Apple).on(:id)
314
+ expect(apple2.name).to eq("Adams Apple 2")
315
+ end
316
+ end
317
+ end
@@ -0,0 +1,168 @@
1
+ require "spec_helper"
2
+
3
+ describe RecordCache::Strategy::UniqueIndexCache do
4
+
5
+ it "should retrieve an Person from the cache" do
6
+ expect{ Person.find_by_name("Fry") }.to miss_cache(Person).on(:name).times(1)
7
+ expect{ Person.find_by_name("Fry") }.to hit_cache(Person).on(:name).times(1)
8
+ end
9
+
10
+ it "should retrieve cloned records" do
11
+ @fry_a = Person.find_by_name("Fry")
12
+ @fry_b = Person.find_by_name("Fry")
13
+ expect(@fry_a).to eq(@fry_b)
14
+ expect(@fry_a.object_id).to_not eq(@fry_b.object_id)
15
+ end
16
+
17
+ context "logging" do
18
+ before(:each) do
19
+ Person.find_by_name("Fry")
20
+ end
21
+
22
+ it "should write full hits to the debug log" do
23
+ expect{ Person.find_by_name("Fry") }.to log(:debug, %(UniqueIndexCache on 'Person.name' hit for ids "Fry"))
24
+ end
25
+
26
+ it "should write full miss to the debug log" do
27
+ expect{ Person.find_by_name("Chase") }.to log(:debug, %(UniqueIndexCache on 'Person.name' miss for ids "Chase"))
28
+ end
29
+
30
+ it "should write partial hits to the debug log" do
31
+ expect{ Person.where(:name => ["Fry", "Chase"]).all }.to log(:debug, %(UniqueIndexCache on 'Person.name' partial hit for ids ["Fry", "Chase"]: missing ["Chase"]))
32
+ end
33
+ end
34
+
35
+ context "cacheable?" do
36
+ before(:each) do
37
+ # fill cache
38
+ @fry = Person.find_by_name("Fry")
39
+ @chase = Person.find_by_name("Chase")
40
+ end
41
+
42
+ # @see https://github.com/orslumen/record-cache/issues/2
43
+ it "should not use the cache when a lock is used" do
44
+ expect{ Person.lock("any_lock").where(:name => "Fry").all }.to_not hit_cache(Person)
45
+ end
46
+
47
+ it "should use the cache when a single id is requested" do
48
+ expect{ Person.where(:name => "Fry").all }.to hit_cache(Person).on(:name).times(1)
49
+ end
50
+
51
+ it "should use the cache when a multiple ids are requested" do
52
+ expect{ Person.where(:name => ["Fry", "Chase"]).all }.to hit_cache(Person).on(:name).times(2)
53
+ end
54
+
55
+ it "should use the cache when a single id is requested and the limit is 1" do
56
+ expect{ Person.where(:name => "Fry").limit(1).all }.to hit_cache(Person).on(:name).times(1)
57
+ end
58
+
59
+ it "should not use the cache when a single id is requested and the limit is > 1" do
60
+ expect{ Person.where(:name => "Fry").limit(2).all }.to_not use_cache(Person).on(:name)
61
+ end
62
+
63
+ it "should not use the cache when multiple ids are requested and the limit is 1" do
64
+ expect{ Person.where(:name => ["Fry", "Chase"]).limit(1).all }.to_not use_cache(Person).on(:name)
65
+ end
66
+
67
+ it "should use the cache when a single id is requested together with other where clauses" do
68
+ expect{ Person.where(:name => "Fry").where(:height => 1.67).all }.to hit_cache(Person).on(:name).times(1)
69
+ end
70
+
71
+ it "should use the cache when a multiple ids are requested together with other where clauses" do
72
+ expect{ Person.where(:name => ["Fry", "Chase"]).where(:height => 1.67).all }.to hit_cache(Person).on(:name).times(2)
73
+ end
74
+
75
+ it "should use the cache when a single id is requested together with (simple) sort clauses" do
76
+ expect{ Person.where(:name => "Fry").order("name ASC").all }.to hit_cache(Person).on(:name).times(1)
77
+ end
78
+
79
+ it "should use the cache when a single id is requested together with (simple) case insensitive sort clauses" do
80
+ expect{ Person.where(:name => "Fry").order("name desc").all }.to hit_cache(Person).on(:name).times(1)
81
+ end
82
+
83
+ it "should use the cache when a single id is requested together with (simple) sort clauses with table prefix" do
84
+ expect{ Person.where(:name => "Fry").order("people.name desc").all }.to hit_cache(Person).on(:name).times(1)
85
+ end
86
+
87
+ it "should not use the cache when a single id is requested together with an unknown sort clause" do
88
+ expect{ Person.where(:name => "Fry").order("lower(people.name) desc").all }.to_not hit_cache(Person).on(:name).times(1)
89
+ end
90
+
91
+ it "should use the cache when a multiple ids are requested together with (simple) sort clauses" do
92
+ expect{ Person.where(:name => ["Fry", "Chase"]).order("name ASC").all }.to hit_cache(Person).on(:name).times(2)
93
+ end
94
+ end
95
+
96
+ context "record_change" do
97
+ before(:each) do
98
+ # fill cache
99
+ @fry = Person.find_by_name("Fry")
100
+ @chase = Person.find_by_name("Chase")
101
+ end
102
+
103
+ it "should invalidate destroyed records" do
104
+ expect{ Person.where(:name => "Fry").all }.to hit_cache(Person).on(:name).times(1)
105
+ @fry.destroy
106
+ expect{ @people = Person.where(:name => "Fry").all }.to miss_cache(Person).on(:name).times(1)
107
+ expect(@people).to be_empty
108
+ # try again, to make sure the "missing record" is not cached
109
+ expect{ Person.where(:name => "Fry").all }.to miss_cache(Person).on(:name).times(1)
110
+ end
111
+
112
+ it "should add updated records directly to the cache" do
113
+ @fry.height = 1.71
114
+ @fry.save!
115
+ expect{ @person = Person.find_by_name("Fry") }.to hit_cache(Person).on(:name).times(1)
116
+ expect(@person.height).to eq(1.71)
117
+ end
118
+
119
+ it "should add created records directly to the cache" do
120
+ Person.create!(:name => "Flower", :birthday => Date.civil(1990,07,29), :height => 1.80)
121
+ expect{ @person = Person.find_by_name("Flower") }.to hit_cache(Person).on(:name).times(1)
122
+ expect(@person.height).to eq(1.80)
123
+ end
124
+
125
+ it "should add updated records to the cache, also when multiple ids are queried" do
126
+ @fry.height = 1.71
127
+ @fry.save!
128
+ expect{ @people = Person.where(:name => ["Fry", "Chase"]).order("id ASC").all }.to hit_cache(Person).on(:name).times(2)
129
+ expect(@people.map(&:height)).to eq([1.71, 1.91])
130
+ end
131
+
132
+ end
133
+
134
+ context "invalidate" do
135
+ before(:each) do
136
+ @fry = Person.find_by_name("Fry")
137
+ @chase = Person.find_by_name("Chase")
138
+ end
139
+
140
+ it "should invalidate single records" do
141
+ Person.record_cache[:name].invalidate("Fry")
142
+ expect{ Person.find_by_name("Fry") }.to miss_cache(Person).on(:name).times(1)
143
+ end
144
+
145
+ it "should only miss the cache for the invalidated record when multiple ids are queried" do
146
+ # miss on 1
147
+ Person.record_cache[:name].invalidate("Fry")
148
+ expect{ Person.where(:name => ["Fry", "Chase"]).all }.to miss_cache(Person).on(:name).times(1)
149
+ # hit on 2
150
+ Person.record_cache[:name].invalidate("Fry")
151
+ expect{ Person.where(:name => ["Fry", "Chase"]).all }.to hit_cache(Person).on(:name).times(1)
152
+ # nothing invalidated, both hit
153
+ expect{ Person.where(:name => ["Fry", "Chase"]).all }.to hit_cache(Person).on(:name).times(2)
154
+ end
155
+
156
+ it "should invalidate records when using update_all" do
157
+ Person.where(:id => ["Fry", "Chase", "Penny"]).all # fill id cache on all Adam Store apples
158
+ expect{ @people = Person.where(:name => ["Fry", "Chase", "Penny"]).order("name ASC").all }.to hit_cache(Person).on(:name).times(2)
159
+ expect(@people.map(&:name)).to eq(["Chase", "Fry", "Penny"])
160
+ # update 2 of the 3 People
161
+ Person.where(:name => ["Fry", "Penny"]).update_all(:height => 1.21)
162
+ expect{ @people = Person.where(:name => ["Fry", "Chase", "Penny"]).order("height ASC").all }.to hit_cache(Person).on(:name).times(1)
163
+ expect(@people.map(&:height)).to eq([1.21, 1.21, 1.91])
164
+ end
165
+
166
+ end
167
+
168
+ end