record-cache 0.1.2 → 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
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,68 @@
1
+ require 'spec_helper'
2
+
3
+ describe RecordCache::Strategy::FullTableCache do
4
+
5
+ it "should retrieve a Language from the cache" do
6
+ expect{ Language.where(:locale => 'en-US').all }.to miss_cache(Language).on(:full_table).times(1)
7
+ expect{ Language.where(:locale => 'en-US').all }.to hit_cache(Language).on(:full_table).times(1)
8
+ end
9
+
10
+ it "should retrieve all Languages from cache" do
11
+ expect{ Language.all }.to miss_cache(Language).on(:full_table).times(1)
12
+ expect{ Language.all }.to hit_cache(Language).on(:full_table).times(1)
13
+ expect(Language.all.map(&:locale).sort).to eq(%w(du-NL en-GB en-US hu))
14
+ end
15
+
16
+ context "logging" do
17
+ it "should write hit to the debug log" do
18
+ Language.all
19
+ expect{ Language.all }.to log(:debug, "FullTableCache hit for model Language")
20
+ end
21
+
22
+ it "should write miss to the debug log" do
23
+ expect{ Language.all }.to log(:debug, "FullTableCache miss for model Language")
24
+ end
25
+ end
26
+
27
+ context "cacheable?" do
28
+ it "should always return true" do
29
+ expect(Language.record_cache[:full_table].cacheable?("any query")).to be_truthy
30
+ end
31
+ end
32
+
33
+ context "record_change" do
34
+ before(:each) do
35
+ @Languages = Language.all
36
+ end
37
+
38
+ it "should invalidate the cache when a record is added" do
39
+ expect{ Language.where(:locale => 'en-US').all }.to hit_cache(Language).on(:full_table).times(1)
40
+ Language.create!(:name => 'Deutsch', :locale => 'de')
41
+ expect{ Language.where(:locale => 'en-US').all }.to miss_cache(Language).on(:full_table).times(1)
42
+ end
43
+
44
+ it "should invalidate the cache when any record is deleted" do
45
+ expect{ Language.where(:locale => 'en-US').all }.to hit_cache(Language).on(:full_table).times(1)
46
+ Language.where(:locale => 'hu').first.destroy
47
+ expect{ Language.where(:locale => 'en-US').all }.to miss_cache(Language).on(:full_table).times(1)
48
+ end
49
+
50
+ it "should invalidate the cache when any record is modified" do
51
+ expect{ Language.where(:locale => 'en-US').all }.to hit_cache(Language).on(:full_table).times(1)
52
+ hungarian = Language.where(:locale => 'hu').first
53
+ hungarian.name = 'Magyar (Magyarorszag)'
54
+ hungarian.save!
55
+ expect{ Language.where(:locale => 'en-US').all }.to miss_cache(Language).on(:full_table).times(1)
56
+ end
57
+ end
58
+
59
+ context "invalidate" do
60
+
61
+ it "should invalidate the full cache" do
62
+ Language.record_cache[:full_table].invalidate(-10) # any id
63
+ expect{ Language.where(:locale => 'en-US').all }.to miss_cache(Language).on(:full_table).times(1)
64
+ end
65
+
66
+ end
67
+
68
+ end
@@ -4,18 +4,18 @@ describe RecordCache::Strategy::IndexCache do
4
4
 
5
5
  context "initialize" do
6
6
  it "should only accept index cache on DB columns" do
7
- lambda { Apple.send(:cache_records, :index => :unknown_column) }.should raise_error("No column found for index 'unknown_column' on Apple.")
7
+ expect{ Apple.send(:cache_records, :index => :unknown_column) }.to raise_error("No column found for index 'unknown_column' on Apple.")
8
8
  end
9
9
 
10
10
  it "should only accept index cache on integer columns" do
11
- lambda { Apple.send(:cache_records, :index => :name) }.should raise_error("Incorrect type (expected integer, found string) for index 'name' on Apple.")
11
+ expect{ Apple.send(:cache_records, :index => :name) }.to raise_error("Incorrect type (expected integer, found string) for index 'name' on Apple.")
12
12
  end
13
13
  end
14
14
 
15
15
  it "should use the id cache to retrieve the actual records" do
16
- lambda { @apples = Apple.where(:store_id => 1).all }.should miss_cache(Apple).on(:store_id).times(1)
17
- lambda { Apple.where(:store_id => 1).all }.should hit_cache(Apple).on(:store_id).times(1)
18
- lambda { Apple.where(:store_id => 1).all }.should hit_cache(Apple).on(:id).times(@apples.size)
16
+ expect{ @apples = Apple.where(:store_id => 1).all }.to miss_cache(Apple).on(:store_id).times(1)
17
+ expect{ Apple.where(:store_id => 1).all }.to hit_cache(Apple).on(:store_id).times(1)
18
+ expect{ Apple.where(:store_id => 1).all }.to hit_cache(Apple).on(:id).times(@apples.size)
19
19
  end
20
20
 
21
21
  context "logging" do
@@ -24,13 +24,11 @@ describe RecordCache::Strategy::IndexCache do
24
24
  end
25
25
 
26
26
  it "should write hit to the debug log" do
27
- mock(RecordCache::Base.logger).debug(/IndexCache hit for rc\/apl\/store_id=1v\d+: found 5 ids|^(?!IndexCache)/).times(any_times)
28
- Apple.where(:store_id => 1).all
27
+ expect{ Apple.where(:store_id => 1).all }.to log(:debug, /IndexCache hit for rc\/apl\/store_id=1v\d+: found 5 ids/)
29
28
  end
30
29
 
31
30
  it "should write miss to the debug log" do
32
- mock(RecordCache::Base.logger).debug(/IndexCache miss for rc\/apl\/store_id=2v\d+: found no ids|^(?!IndexCache)/).times(any_times)
33
- Apple.where(:store_id => 2).all
31
+ expect{ Apple.where(:store_id => 2).all }.to log(:debug, /IndexCache miss for rc\/apl\/store_id=2v\d+: found no ids/)
34
32
  end
35
33
  end
36
34
 
@@ -41,24 +39,25 @@ describe RecordCache::Strategy::IndexCache do
41
39
  end
42
40
 
43
41
  it "should hit the cache for a single index id" do
44
- lambda { Apple.where(:store_id => 1).all }.should hit_cache(Apple).on(:store_id).times(1)
42
+ expect{ Apple.where(:store_id => 1).all }.to hit_cache(Apple).on(:store_id).times(1)
45
43
  end
46
44
 
47
45
  it "should hit the cache for a single index id with other where clauses" do
48
- lambda { Apple.where(:store_id => 1).where(:name => "applegate").all }.should hit_cache(Apple).on(:store_id).times(1)
46
+ expect{ Apple.where(:store_id => 1).where(:name => "applegate").all }.to hit_cache(Apple).on(:store_id).times(1)
49
47
  end
50
48
 
51
49
  it "should hit the cache for a single index id with (simple) sort clauses" do
52
- lambda { Apple.where(:store_id => 1).order("name ASC").all }.should hit_cache(Apple).on(:store_id).times(1)
50
+ expect{ Apple.where(:store_id => 1).order("name ASC").all }.to hit_cache(Apple).on(:store_id).times(1)
53
51
  end
54
52
 
55
- it "should not hit the cache for a single index id with limit" do
56
- lambda { Apple.where(:store_id => 1).limit(1).all }.should_not hit_cache(Apple).on(:store_id)
53
+ #Allow limit == 1 by filtering records after cache hit. Needed for has_one
54
+ it "should not hit the cache for a single index id with limit > 0" do
55
+ expect{ Apple.where(:store_id => 1).limit(2).all }.to_not hit_cache(Apple).on(:store_id)
57
56
  end
58
57
 
59
58
  it "should not hit the cache when an :id where clause is defined" do
60
59
  # this query should make use of the :id cache, which is faster
61
- lambda { Apple.where(:store_id => 1).where(:id => 1).all }.should_not hit_cache(Apple).on(:store_id)
60
+ expect{ Apple.where(:store_id => 1).where(:id => 1).all }.to_not hit_cache(Apple).on(:store_id)
62
61
  end
63
62
  end
64
63
 
@@ -77,14 +76,14 @@ describe RecordCache::Strategy::IndexCache do
77
76
  @destroyed.destroy
78
77
  # check the cache hit/miss on the index that contained that apple
79
78
  if fresh
80
- lambda { @apples = Apple.where(:store_id => 1).order('id ASC').all }.should hit_cache(Apple).on(:store_id).times(1)
79
+ expect{ @apples = Apple.where(:store_id => 1).order('id ASC').all }.to hit_cache(Apple).on(:store_id).times(1)
81
80
  else
82
- lambda { @apples = Apple.where(:store_id => 1).order('id ASC').all }.should miss_cache(Apple).on(:store_id).times(1)
81
+ expect{ @apples = Apple.where(:store_id => 1).order('id ASC').all }.to miss_cache(Apple).on(:store_id).times(1)
83
82
  end
84
- @apples.size.should == @store1_apples.size - 1
85
- @apples.map(&:id).should == @store1_apples.map(&:id) - [@destroyed.id]
83
+ expect(@apples.size).to eq(@store1_apples.size - 1)
84
+ expect(@apples.map(&:id)).to eq(@store1_apples.map(&:id) - [@destroyed.id])
86
85
  # and the index should be cached again
87
- lambda { Apple.where(:store_id => 1).all }.should hit_cache(Apple).on(:store_id).times(1)
86
+ expect{ Apple.where(:store_id => 1).all }.to hit_cache(Apple).on(:store_id).times(1)
88
87
  end
89
88
 
90
89
  it "should #{fresh ? 'update' : 'invalidate'} the index when a record in the index is created and the current index is #{fresh ? '' : 'not '}fresh" do
@@ -94,14 +93,14 @@ describe RecordCache::Strategy::IndexCache do
94
93
  @new_apple_in_store1 = Apple.create!(:name => "Fresh Apple", :store_id => 1)
95
94
  # check the cache hit/miss on the index that contains that apple
96
95
  if fresh
97
- lambda { @apples = Apple.where(:store_id => 1).order('id ASC').all }.should hit_cache(Apple).on(:store_id).times(1)
96
+ expect{ @apples = Apple.where(:store_id => 1).order('id ASC').all }.to hit_cache(Apple).on(:store_id).times(1)
98
97
  else
99
- lambda { @apples = Apple.where(:store_id => 1).order('id ASC').all }.should miss_cache(Apple).on(:store_id).times(1)
98
+ expect{ @apples = Apple.where(:store_id => 1).order('id ASC').all }.to miss_cache(Apple).on(:store_id).times(1)
100
99
  end
101
- @apples.size.should == @store1_apples.size + 1
102
- @apples.map(&:id).should == @store1_apples.map(&:id) + [@new_apple_in_store1.id]
100
+ expect(@apples.size).to eq(@store1_apples.size + 1)
101
+ expect(@apples.map(&:id)).to eq(@store1_apples.map(&:id) + [@new_apple_in_store1.id])
103
102
  # and the index should be cached again
104
- lambda { Apple.where(:store_id => 1).all }.should hit_cache(Apple).on(:store_id).times(1)
103
+ expect{ Apple.where(:store_id => 1).all }.to hit_cache(Apple).on(:store_id).times(1)
105
104
  end
106
105
 
107
106
  it "should #{fresh ? 'update' : 'invalidate'} two indexes when the indexed value is updated and the current index is #{fresh ? '' : 'not '}fresh" do
@@ -114,19 +113,19 @@ describe RecordCache::Strategy::IndexCache do
114
113
  @apple_moved_from_store1_to_store2.save!
115
114
  # check the cache hit/miss on the indexes that contained/contains that apple
116
115
  if fresh
117
- lambda { @apples1 = Apple.where(:store_id => 1).order('id ASC').all }.should hit_cache(Apple).on(:store_id).times(1)
118
- lambda { @apples2 = Apple.where(:store_id => 2).order('id ASC').all }.should hit_cache(Apple).on(:store_id).times(1)
116
+ expect{ @apples1 = Apple.where(:store_id => 1).order('id ASC').all }.to hit_cache(Apple).on(:store_id).times(1)
117
+ expect{ @apples2 = Apple.where(:store_id => 2).order('id ASC').all }.to hit_cache(Apple).on(:store_id).times(1)
119
118
  else
120
- lambda { @apples1 = Apple.where(:store_id => 1).order('id ASC').all }.should miss_cache(Apple).on(:store_id).times(1)
121
- lambda { @apples2 = Apple.where(:store_id => 2).order('id ASC').all }.should miss_cache(Apple).on(:store_id).times(1)
119
+ expect{ @apples1 = Apple.where(:store_id => 1).order('id ASC').all }.to miss_cache(Apple).on(:store_id).times(1)
120
+ expect{ @apples2 = Apple.where(:store_id => 2).order('id ASC').all }.to miss_cache(Apple).on(:store_id).times(1)
122
121
  end
123
- @apples1.size.should == @store1_apples.size - 1
124
- @apples2.size.should == @store2_apples.size + 1
125
- @apples1.map(&:id).should == @store1_apples.map(&:id) - [@apple_moved_from_store1_to_store2.id]
126
- @apples2.map(&:id).should == [@apple_moved_from_store1_to_store2.id] + @store2_apples.map(&:id)
122
+ expect(@apples1.size).to eq(@store1_apples.size - 1)
123
+ expect(@apples2.size).to eq(@store2_apples.size + 1)
124
+ expect(@apples1.map(&:id)).to eq(@store1_apples.map(&:id) - [@apple_moved_from_store1_to_store2.id])
125
+ expect(@apples2.map(&:id)).to eq([@apple_moved_from_store1_to_store2.id] + @store2_apples.map(&:id))
127
126
  # and the index should be cached again
128
- lambda { Apple.where(:store_id => 1).all }.should hit_cache(Apple).on(:store_id).times(1)
129
- lambda { Apple.where(:store_id => 2).all }.should hit_cache(Apple).on(:store_id).times(1)
127
+ expect{ Apple.where(:store_id => 1).all }.to hit_cache(Apple).on(:store_id).times(1)
128
+ expect{ Apple.where(:store_id => 2).all }.to hit_cache(Apple).on(:store_id).times(1)
130
129
  end
131
130
 
132
131
  it "should #{fresh ? 'update' : 'invalidate'} multiple indexes when several values on different indexed attributes are updated at once and one of the indexes is #{fresh ? '' : 'not '}fresh" do
@@ -142,28 +141,28 @@ describe RecordCache::Strategy::IndexCache do
142
141
  @apple_moved_from_s1to2_p5to4.person_id = 4
143
142
  @apple_moved_from_s1to2_p5to4.save!
144
143
  # check the cache hit/miss on the indexes that contained/contains that apple
145
- lambda { @apples_s1 = Apple.where(:store_id => 1).order('id ASC').all }.should hit_cache(Apple).on(:store_id).times(1)
146
- lambda { @apples_s2 = Apple.where(:store_id => 2).order('id ASC').all }.should hit_cache(Apple).on(:store_id).times(1)
144
+ expect{ @apples_s1 = Apple.where(:store_id => 1).order('id ASC').all }.to hit_cache(Apple).on(:store_id).times(1)
145
+ expect{ @apples_s2 = Apple.where(:store_id => 2).order('id ASC').all }.to hit_cache(Apple).on(:store_id).times(1)
147
146
  if fresh
148
- lambda { @apples_p1 = Apple.where(:person_id => 4).order('id ASC').all }.should hit_cache(Apple).on(:person_id).times(1)
149
- lambda { @apples_p2 = Apple.where(:person_id => 5).order('id ASC').all }.should hit_cache(Apple).on(:person_id).times(1)
147
+ expect{ @apples_p1 = Apple.where(:person_id => 4).order('id ASC').all }.to hit_cache(Apple).on(:person_id).times(1)
148
+ expect{ @apples_p2 = Apple.where(:person_id => 5).order('id ASC').all }.to hit_cache(Apple).on(:person_id).times(1)
150
149
  else
151
- lambda { @apples_p1 = Apple.where(:person_id => 4).order('id ASC').all }.should miss_cache(Apple).on(:person_id).times(1)
152
- lambda { @apples_p2 = Apple.where(:person_id => 5).order('id ASC').all }.should miss_cache(Apple).on(:person_id).times(1)
150
+ expect{ @apples_p1 = Apple.where(:person_id => 4).order('id ASC').all }.to miss_cache(Apple).on(:person_id).times(1)
151
+ expect{ @apples_p2 = Apple.where(:person_id => 5).order('id ASC').all }.to miss_cache(Apple).on(:person_id).times(1)
153
152
  end
154
- @apples_s1.size.should == @store1_apples.size - 1
155
- @apples_s2.size.should == @store2_apples.size + 1
156
- @apples_p1.size.should == @person4_apples.size + 1
157
- @apples_p2.size.should == @person5_apples.size - 1
158
- @apples_s1.map(&:id).should == @store1_apples.map(&:id) - [@apple_moved_from_s1to2_p5to4.id]
159
- @apples_s2.map(&:id).should == [@apple_moved_from_s1to2_p5to4.id] + @store2_apples.map(&:id)
160
- @apples_p1.map(&:id).should == ([@apple_moved_from_s1to2_p5to4.id] + @person4_apples.map(&:id)).sort
161
- @apples_p2.map(&:id).should == (@person5_apples.map(&:id) - [@apple_moved_from_s1to2_p5to4.id]).sort
153
+ expect(@apples_s1.size).to eq(@store1_apples.size - 1)
154
+ expect(@apples_s2.size).to eq(@store2_apples.size + 1)
155
+ expect(@apples_p1.size).to eq(@person4_apples.size + 1)
156
+ expect(@apples_p2.size).to eq(@person5_apples.size - 1)
157
+ expect(@apples_s1.map(&:id)).to eq(@store1_apples.map(&:id) - [@apple_moved_from_s1to2_p5to4.id])
158
+ expect(@apples_s2.map(&:id)).to eq([@apple_moved_from_s1to2_p5to4.id] + @store2_apples.map(&:id))
159
+ expect(@apples_p1.map(&:id)).to eq(([@apple_moved_from_s1to2_p5to4.id] + @person4_apples.map(&:id)).sort)
160
+ expect(@apples_p2.map(&:id)).to eq( (@person5_apples.map(&:id) - [@apple_moved_from_s1to2_p5to4.id]).sort)
162
161
  # and the index should be cached again
163
- lambda { Apple.where(:store_id => 1).all }.should hit_cache(Apple).on(:store_id).times(1)
164
- lambda { Apple.where(:store_id => 2).all }.should hit_cache(Apple).on(:store_id).times(1)
165
- lambda { Apple.where(:person_id => 4).all }.should hit_cache(Apple).on(:person_id).times(1)
166
- lambda { Apple.where(:person_id => 5).all }.should hit_cache(Apple).on(:person_id).times(1)
162
+ expect{ Apple.where(:store_id => 1).all }.to hit_cache(Apple).on(:store_id).times(1)
163
+ expect{ Apple.where(:store_id => 2).all }.to hit_cache(Apple).on(:store_id).times(1)
164
+ expect{ Apple.where(:person_id => 4).all }.to hit_cache(Apple).on(:person_id).times(1)
165
+ expect{ Apple.where(:person_id => 5).all }.to hit_cache(Apple).on(:person_id).times(1)
167
166
  end
168
167
  end
169
168
 
@@ -171,14 +170,14 @@ describe RecordCache::Strategy::IndexCache do
171
170
  # destroy an apple of store 2
172
171
  @store2_apples.first.destroy
173
172
  # index of apples of store 1 are not affected
174
- lambda { @apples = Apple.where(:store_id => 1).order('id ASC').all }.should hit_cache(Apple).on(:store_id).times(1)
173
+ expect{ @apples = Apple.where(:store_id => 1).order('id ASC').all }.to hit_cache(Apple).on(:store_id).times(1)
175
174
  end
176
175
 
177
176
  it "should leave the index alone when a record outside the index is created" do
178
177
  # create an apple for store 2
179
178
  Apple.create!(:name => "Fresh Apple", :store_id => 2)
180
179
  # index of apples of store 1 are not affected
181
- lambda { @apples = Apple.where(:store_id => 1).order('id ASC').all }.should hit_cache(Apple).on(:store_id).times(1)
180
+ expect{ @apples = Apple.where(:store_id => 1).order('id ASC').all }.to hit_cache(Apple).on(:store_id).times(1)
182
181
  end
183
182
  end
184
183
 
@@ -186,37 +185,81 @@ describe RecordCache::Strategy::IndexCache do
186
185
  before(:each) do
187
186
  @store1_apples = Apple.where(:store_id => 1).all
188
187
  @store2_apples = Apple.where(:store_id => 2).all
188
+ @address_1 = Address.where(:store_id => 1).all
189
+ @address_2 = Address.where(:store_id => 2).all
189
190
  end
190
191
 
191
192
  it "should invalidate single index" do
192
193
  Apple.record_cache[:store_id].invalidate(1)
193
- lambda{ Apple.where(:store_id => 1).all }.should miss_cache(Apple).on(:store_id).times(1)
194
+ expect{ Apple.where(:store_id => 1).all }.to miss_cache(Apple).on(:store_id).times(1)
194
195
  end
195
196
 
196
197
  it "should invalidate indexes when using update_all" do
197
- pending "is there a performant way to invalidate index caches within update_all? only the new value is available, so we should query the old values..." do
198
- # update 2 apples for index values store 1 and store 2
199
- Apple.where(:id => [@store1_apples.first.id, @store2_apples.first.id]).update_all(:store_id => 3)
200
- lambda{ @apples_1 = Apple.where(:store_id => 1).all }.should miss_cache(Apple).on(:store_id).times(1)
201
- lambda{ @apples_2 = Apple.where(:store_id => 2).all }.should miss_cache(Apple).on(:store_id).times(1)
202
- @apples_1.map(&:id).sort.should == @store1_apples[1..-1].sort
203
- @apples_2.map(&:id).sort.should == @store2_apples[1..-1].sort
204
- end
198
+ pending "is there a performant way to invalidate index caches within update_all? only the new value is available, so we should query the old values..."
199
+ # update 2 apples for index values store 1 and store 2
200
+ Apple.where(:id => [@store1_apples.first.id, @store2_apples.first.id]).update_all(:store_id => 3)
201
+ expect{ @apples_1 = Apple.where(:store_id => 1).all }.to miss_cache(Apple).on(:store_id).times(1)
202
+ expect{ @apples_2 = Apple.where(:store_id => 2).all }.to miss_cache(Apple).on(:store_id).times(1)
203
+ expect(@apples_1.map(&:id).sort).to eq(@store1_apples[1..-1].sort)
204
+ expect(@apples_2.map(&:id).sort).to eq(@store2_apples[1..-1].sort)
205
205
  end
206
206
 
207
207
  it "should invalidate reflection indexes when a has_many relation is updated" do
208
208
  # assign different apples to store 2
209
- lambda{ Apple.where(:store_id => 1).all }.should hit_cache(Apple).on(:store_id).times(1)
209
+ expect{ Apple.where(:store_id => 1).first }.to hit_cache(Apple).on(:store_id).times(1)
210
210
  store2_apple_ids = @store2_apples.map(&:id).sort
211
211
  store1 = Store.find(1)
212
212
  store1.apple_ids = store2_apple_ids
213
213
  store1.save!
214
214
  # apples in Store 1 should be all (only) the apples that were in Store 2 (cache invalidated)
215
- lambda{ @apples_1 = Apple.where(:store_id => 1).all }.should miss_cache(Apple).on(:store_id).times(1)
216
- @apples_1.map(&:id).sort.should == store2_apple_ids
215
+ expect{ @apples_1 = Apple.where(:store_id => 1).all }.to miss_cache(Apple).on(:store_id).times(1)
216
+ expect(@apples_1.map(&:id).sort).to eq(store2_apple_ids)
217
217
  # there are no apples in Store 2 anymore (incremental cache update, as each apples in store 2 was saved separately)
218
- lambda{ @apples_2 = Apple.where(:store_id => 2).all }.should hit_cache(Apple).on(:store_id).times(1)
219
- @apples_2.should == []
218
+ expect{ @apples_2 = Apple.where(:store_id => 2).all }.to hit_cache(Apple).on(:store_id).times(1)
219
+ expect(@apples_2).to eq([])
220
+ end
221
+
222
+ it "should invalidate reflection indexes when a has_one relation is updated" do
223
+ # assign different address to store 2
224
+ expect{ Address.where(:store_id => 1).limit(1).first }.to hit_cache(Address).on(:store_id).times(1)
225
+ store2 = Store.find(2)
226
+ store2_address = store2.address
227
+ Address.where(:store_id => 1).first.id == 1
228
+ store1 = Store.find(1)
229
+ store1.address = store2_address
230
+ store1.save!
231
+ Address.where(:store_id => 1).first.id == 2
232
+ # address for Store 1 should be the address that was for Store 2 (cache invalidated)
233
+ expect{ @address_1 = Address.where(:store_id => 1).first }.to hit_cache(Address).on(:store_id).times(1)
234
+ expect(@address_1.id).to eq(store2_address.id)
235
+ # there are no address in Store 2 anymore (incremental cache update, as address for store 2 was saved separately)
236
+ expect{ @address_2 = Address.where(:store_id => 2).first }.to hit_cache(Address).on(:store_id).times(1)
237
+ expect(@address_2).to be_nil
238
+ end
239
+
240
+ # see https://github.com/orslumen/record-cache/issues/19
241
+ it "should work with serialized object" do
242
+ address = Address.find(3) # not from cache
243
+ address = Address.find(3) # from cache
244
+ expect(address.location[:latitue]).to eq(27.175015)
245
+ expect(address.location[:dms_lat]).to eq(%(27\u00B0 10' 30.0540" N))
246
+ address.name = 'updated name'
247
+ address.save!
248
+ end
249
+ end
250
+
251
+ context 'subclassing' do
252
+ it "should delegate cache updates to the base class" do
253
+ class RedDelicious < Apple; end
254
+ apple = Apple.find(1)
255
+ delicious = RedDelicious.find(1)
256
+ store_id = apple.store_id
257
+ delicious.store_id = 100
258
+ delicious.save
259
+ apple = Apple.find(1)
260
+ expect(apple.store_id).to_not eq(store_id)
261
+ apple.store_id = store_id
262
+ apple.save
220
263
  end
221
264
  end
222
265
 
@@ -0,0 +1,83 @@
1
+ require 'spec_helper'
2
+
3
+ # This describes the behaviour as expected when ActiveRecord::QueryCache is
4
+ # enabled. ActiveRecord::QueryCache is enabled by default in rails via a
5
+ # middleware. During the scope of a request that cache is used.
6
+ #
7
+ # In console mode (or within e.g. a cron job) QueryCache isn't enabled.
8
+ # You can still take advantage of this cache by executing
9
+ #
10
+ # ActiveRecord::Base.cache do
11
+ # # your queries
12
+ # end
13
+ #
14
+ # Be aware that though that during the execution of the block if updates
15
+ # happen to records by another process, while you have already got
16
+ # references to that records in QueryCache, that you won't see the changes
17
+ # made by the other process.
18
+ describe "QueryCache" do
19
+
20
+ it "should retrieve a record from the QueryCache" do
21
+ ActiveRecord::Base.cache do
22
+ expect{ Store.find(1) }.to miss_cache(Store).on(:id).times(1)
23
+ second_lookup = expect{ Store.find(1) }
24
+ second_lookup.to miss_cache(Store).times(0)
25
+ second_lookup.to hit_cache(Store).on(:id).times(0)
26
+ end
27
+ end
28
+
29
+ it "should maintain object identity when the same query is used" do
30
+ ActiveRecord::Base.cache do
31
+ @store_1 = Store.find(1)
32
+ @store_2 = Store.find(1)
33
+ expect(@store_1).to eq(@store_2)
34
+ expect(@store_1.object_id).to eq(@store_2.object_id)
35
+ end
36
+ end
37
+
38
+ context "record_change" do
39
+ it "should clear the query cache completely when a record is created" do
40
+ ActiveRecord::Base.cache do
41
+ init_query_cache
42
+ expect{ Store.find(2) }.to hit_cache(Store).times(0)
43
+ expect{ Apple.find(1) }.to hit_cache(Apple).times(0)
44
+ Store.create!(:name => "New Apple Store")
45
+ expect{ Store.find(2) }.to hit_cache(Store).times(1)
46
+ expect{ Apple.find(1) }.to hit_cache(Apple).times(1)
47
+ end
48
+ end
49
+
50
+ it "should clear the query cache completely when a record is updated" do
51
+ ActiveRecord::Base.cache do
52
+ init_query_cache
53
+ expect{ Store.find(2) }.to hit_cache(Store).times(0)
54
+ expect{ Apple.find(1) }.to hit_cache(Apple).times(0)
55
+ @store1.name = "Store E"
56
+ @store1.save!
57
+ expect{ Store.find(2) }.to hit_cache(Store).times(1)
58
+ expect{ Apple.find(1) }.to hit_cache(Apple).times(1)
59
+ end
60
+ end
61
+
62
+ it "should clear the query cache completely when a record is destroyed" do
63
+ ActiveRecord::Base.cache do
64
+ init_query_cache
65
+ expect{ Store.find(2) }.to hit_cache(Store).times(0)
66
+ expect{ Apple.find(1) }.to hit_cache(Apple).times(0)
67
+ @store1.destroy
68
+ expect{ Store.find(2) }.to hit_cache(Store).times(1)
69
+ expect{ Apple.find(1) }.to hit_cache(Apple).times(1)
70
+ end
71
+ end
72
+ end
73
+
74
+ private
75
+
76
+ # Cache a few objects in QueryCache to test with
77
+ def init_query_cache
78
+ @store1 = Store.find(1)
79
+ @store2 = Store.find(2)
80
+ @apple1 = Apple.find(1)
81
+ end
82
+
83
+ end