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.
- checksums.yaml +15 -0
- data/lib/record_cache.rb +2 -1
- data/lib/record_cache/base.rb +63 -22
- data/lib/record_cache/datastore/active_record.rb +5 -3
- data/lib/record_cache/datastore/active_record_30.rb +95 -38
- data/lib/record_cache/datastore/active_record_31.rb +157 -54
- data/lib/record_cache/datastore/active_record_32.rb +444 -0
- data/lib/record_cache/dispatcher.rb +47 -47
- data/lib/record_cache/multi_read.rb +14 -1
- data/lib/record_cache/query.rb +36 -25
- data/lib/record_cache/statistics.rb +5 -5
- data/lib/record_cache/strategy/base.rb +49 -19
- data/lib/record_cache/strategy/full_table_cache.rb +81 -0
- data/lib/record_cache/strategy/index_cache.rb +38 -36
- data/lib/record_cache/strategy/unique_index_cache.rb +130 -0
- data/lib/record_cache/strategy/util.rb +12 -12
- data/lib/record_cache/test/resettable_version_store.rb +2 -9
- data/lib/record_cache/version.rb +1 -1
- data/lib/record_cache/version_store.rb +23 -16
- data/spec/db/schema.rb +12 -0
- data/spec/db/seeds.rb +10 -0
- data/spec/lib/active_record/visitor_spec.rb +22 -0
- data/spec/lib/base_spec.rb +21 -0
- data/spec/lib/dispatcher_spec.rb +24 -46
- data/spec/lib/multi_read_spec.rb +6 -6
- data/spec/lib/query_spec.rb +43 -43
- data/spec/lib/statistics_spec.rb +28 -28
- data/spec/lib/strategy/base_spec.rb +98 -87
- data/spec/lib/strategy/full_table_cache_spec.rb +68 -0
- data/spec/lib/strategy/index_cache_spec.rb +112 -69
- data/spec/lib/strategy/query_cache_spec.rb +83 -0
- data/spec/lib/strategy/unique_index_on_id_cache_spec.rb +317 -0
- data/spec/lib/strategy/unique_index_on_string_cache_spec.rb +168 -0
- data/spec/lib/strategy/util_spec.rb +67 -49
- data/spec/lib/version_store_spec.rb +22 -41
- data/spec/models/address.rb +9 -0
- data/spec/models/apple.rb +1 -1
- data/spec/models/banana.rb +21 -2
- data/spec/models/language.rb +5 -0
- data/spec/models/person.rb +1 -1
- data/spec/models/store.rb +2 -1
- data/spec/spec_helper.rb +7 -4
- data/spec/support/after_commit.rb +2 -0
- data/spec/support/matchers/hit_cache_matcher.rb +10 -6
- data/spec/support/matchers/log.rb +45 -0
- data/spec/support/matchers/miss_cache_matcher.rb +10 -6
- data/spec/support/matchers/use_cache_matcher.rb +10 -6
- metadata +156 -161
- data/lib/record_cache/strategy/id_cache.rb +0 -93
- data/lib/record_cache/strategy/request_cache.rb +0 -49
- data/spec/lib/strategy/id_cache_spec.rb +0 -168
- 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
|
-
|
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
|
-
|
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
|
-
|
17
|
-
|
18
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
56
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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.
|
85
|
-
@apples.map(&: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
|
-
|
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
|
-
|
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
|
-
|
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.
|
102
|
-
@apples.map(&: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
|
-
|
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
|
-
|
118
|
-
|
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
|
-
|
121
|
-
|
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.
|
124
|
-
@apples2.size.
|
125
|
-
@apples1.map(&:id).
|
126
|
-
@apples2.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
|
-
|
129
|
-
|
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
|
-
|
146
|
-
|
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
|
-
|
149
|
-
|
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
|
-
|
152
|
-
|
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.
|
155
|
-
@apples_s2.size.
|
156
|
-
@apples_p1.size.
|
157
|
-
@apples_p2.size.
|
158
|
-
@apples_s1.map(&:id).
|
159
|
-
@apples_s2.map(&:id).
|
160
|
-
@apples_p1.map(&:id).
|
161
|
-
@apples_p2.map(&:id).
|
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
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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..."
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
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
|
-
|
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
|
-
|
216
|
-
@apples_1.map(&:id).sort.
|
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
|
-
|
219
|
-
@apples_2.
|
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
|