low_card_tables 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 (61) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +18 -0
  3. data/.travis.yml +59 -0
  4. data/Gemfile +17 -0
  5. data/LICENSE +21 -0
  6. data/README.md +75 -0
  7. data/Rakefile +6 -0
  8. data/lib/low_card_tables.rb +72 -0
  9. data/lib/low_card_tables/active_record/base.rb +55 -0
  10. data/lib/low_card_tables/active_record/migrations.rb +223 -0
  11. data/lib/low_card_tables/active_record/relation.rb +35 -0
  12. data/lib/low_card_tables/active_record/scoping.rb +87 -0
  13. data/lib/low_card_tables/errors.rb +74 -0
  14. data/lib/low_card_tables/has_low_card_table/base.rb +114 -0
  15. data/lib/low_card_tables/has_low_card_table/low_card_association.rb +273 -0
  16. data/lib/low_card_tables/has_low_card_table/low_card_associations_manager.rb +143 -0
  17. data/lib/low_card_tables/has_low_card_table/low_card_dynamic_method_manager.rb +224 -0
  18. data/lib/low_card_tables/has_low_card_table/low_card_objects_manager.rb +80 -0
  19. data/lib/low_card_tables/low_card_table/base.rb +184 -0
  20. data/lib/low_card_tables/low_card_table/cache.rb +214 -0
  21. data/lib/low_card_tables/low_card_table/cache_expiration/exponential_cache_expiration_policy.rb +151 -0
  22. data/lib/low_card_tables/low_card_table/cache_expiration/fixed_cache_expiration_policy.rb +23 -0
  23. data/lib/low_card_tables/low_card_table/cache_expiration/has_cache_expiration.rb +100 -0
  24. data/lib/low_card_tables/low_card_table/cache_expiration/no_caching_expiration_policy.rb +13 -0
  25. data/lib/low_card_tables/low_card_table/cache_expiration/unlimited_cache_expiration_policy.rb +13 -0
  26. data/lib/low_card_tables/low_card_table/row_collapser.rb +175 -0
  27. data/lib/low_card_tables/low_card_table/row_manager.rb +681 -0
  28. data/lib/low_card_tables/low_card_table/table_unique_index.rb +134 -0
  29. data/lib/low_card_tables/version.rb +4 -0
  30. data/lib/low_card_tables/version_support.rb +52 -0
  31. data/low_card_tables.gemspec +69 -0
  32. data/spec/low_card_tables/helpers/database_helper.rb +148 -0
  33. data/spec/low_card_tables/helpers/query_spy_helper.rb +47 -0
  34. data/spec/low_card_tables/helpers/system_helpers.rb +63 -0
  35. data/spec/low_card_tables/system/basic_system_spec.rb +254 -0
  36. data/spec/low_card_tables/system/bulk_system_spec.rb +334 -0
  37. data/spec/low_card_tables/system/caching_system_spec.rb +531 -0
  38. data/spec/low_card_tables/system/migrations_system_spec.rb +747 -0
  39. data/spec/low_card_tables/system/options_system_spec.rb +581 -0
  40. data/spec/low_card_tables/system/queries_system_spec.rb +142 -0
  41. data/spec/low_card_tables/system/validations_system_spec.rb +88 -0
  42. data/spec/low_card_tables/unit/active_record/base_spec.rb +53 -0
  43. data/spec/low_card_tables/unit/active_record/migrations_spec.rb +207 -0
  44. data/spec/low_card_tables/unit/active_record/relation_spec.rb +47 -0
  45. data/spec/low_card_tables/unit/active_record/scoping_spec.rb +101 -0
  46. data/spec/low_card_tables/unit/has_low_card_table/base_spec.rb +79 -0
  47. data/spec/low_card_tables/unit/has_low_card_table/low_card_association_spec.rb +287 -0
  48. data/spec/low_card_tables/unit/has_low_card_table/low_card_associations_manager_spec.rb +190 -0
  49. data/spec/low_card_tables/unit/has_low_card_table/low_card_dynamic_method_manager_spec.rb +234 -0
  50. data/spec/low_card_tables/unit/has_low_card_table/low_card_objects_manager_spec.rb +70 -0
  51. data/spec/low_card_tables/unit/low_card_table/base_spec.rb +207 -0
  52. data/spec/low_card_tables/unit/low_card_table/cache_expiration/exponential_cache_expiration_policy_spec.rb +128 -0
  53. data/spec/low_card_tables/unit/low_card_table/cache_expiration/fixed_cache_expiration_policy_spec.rb +25 -0
  54. data/spec/low_card_tables/unit/low_card_table/cache_expiration/has_cache_expiration_policy_spec.rb +100 -0
  55. data/spec/low_card_tables/unit/low_card_table/cache_expiration/no_caching_expiration_policy_spec.rb +14 -0
  56. data/spec/low_card_tables/unit/low_card_table/cache_expiration/unlimited_cache_expiration_policy_spec.rb +14 -0
  57. data/spec/low_card_tables/unit/low_card_table/cache_spec.rb +282 -0
  58. data/spec/low_card_tables/unit/low_card_table/row_collapser_spec.rb +109 -0
  59. data/spec/low_card_tables/unit/low_card_table/row_manager_spec.rb +918 -0
  60. data/spec/low_card_tables/unit/low_card_table/table_unique_index_spec.rb +117 -0
  61. metadata +206 -0
@@ -0,0 +1,531 @@
1
+ require 'low_card_tables'
2
+ require 'low_card_tables/helpers/database_helper'
3
+ require 'low_card_tables/helpers/system_helpers'
4
+ require 'low_card_tables/helpers/query_spy_helper'
5
+
6
+ describe "LowCardTables caching" do
7
+ include LowCardTables::Helpers::SystemHelpers
8
+
9
+ before :each do
10
+ @dh = LowCardTables::Helpers::DatabaseHelper.new
11
+ @dh.setup_activerecord!
12
+
13
+ create_standard_system_spec_tables!
14
+ create_standard_system_spec_models!
15
+ end
16
+
17
+ after :each do
18
+ drop_standard_system_spec_tables!
19
+ end
20
+
21
+ def create_basic_user(name = 'User1')
22
+ out = ::User.new
23
+ out.name = 'User1'
24
+ out.deleted = false
25
+ out.deceased = false
26
+ out.gender = 'female'
27
+ out.donation_level = 3
28
+ out.save!
29
+
30
+ out
31
+ end
32
+
33
+ it "should have an explicit cache-flush call that works" do
34
+ LowCardTables::Helpers::QuerySpyHelper.with_query_spy("lctables_spec_user_statuses") do |spy|
35
+ create_basic_user("User1")
36
+
37
+ mid_count = spy.call_count
38
+
39
+ create_basic_user("User2")
40
+
41
+ spy.call_count.should == mid_count
42
+
43
+ ::UserStatus.low_card_flush_cache!
44
+ create_basic_user("User3")
45
+
46
+ spy.call_count.should > mid_count
47
+ end
48
+ end
49
+
50
+ it "should cache low-card rows in memory" do
51
+ LowCardTables::Helpers::QuerySpyHelper.with_query_spy("lctables_spec_user_statuses") do |spy|
52
+ spy.call_count.should == 0
53
+
54
+ user1 = create_basic_user
55
+
56
+ mid_calls = spy.call_count
57
+ mid_calls.should > 0
58
+
59
+ user2 = create_basic_user('User2')
60
+
61
+ spy.call_count.should == mid_calls
62
+ end
63
+ end
64
+
65
+ it "should purge its cache efficiently when adding a new row" do
66
+ LowCardTables::Helpers::QuerySpyHelper.with_query_spy("lctables_spec_user_statuses") do |spy|
67
+ spy.call_count.should == 0
68
+
69
+ user1 = create_basic_user
70
+
71
+ mid_calls = spy.call_count
72
+ mid_calls.should > 0
73
+
74
+ user2 = ::User.new
75
+ user2.name = 'User2'
76
+ user2.deleted = false
77
+ user2.deceased = false
78
+ user2.gender = 'male'
79
+ user2.donation_level = 7
80
+ user2.save!
81
+
82
+ # We allow two calls here because we need it for our double-checked locking pattern.
83
+ (spy.call_count - mid_calls).should <= 2
84
+ end
85
+ end
86
+
87
+ it "should handle the situation where a row in the database has a low-card ID that's not in cache" do
88
+ LowCardTables::Helpers::QuerySpyHelper.with_query_spy("lctables_spec_user_statuses") do |spy|
89
+ spy.call_count.should == 0
90
+
91
+ user1 = create_basic_user
92
+
93
+ mid_calls = spy.call_count
94
+ mid_calls.should > 0
95
+
96
+ new_status = ::UserStatusBackdoor.new
97
+ new_status.deleted = false
98
+ new_status.deceased = false
99
+ new_status.gender = 'male'
100
+ new_status.donation_level = 7
101
+ new_status.save!
102
+
103
+ old_status_id = user1.user_status_id
104
+ ::User.where([ "id = :id", { :id => user1.id } ]).update_all([ "user_status_id = :new_status_id", { :new_status_id => new_status.id } ])
105
+ user1.user_status_id.should == old_status_id # make sure we didn't touch the existing object
106
+
107
+ # Make sure we didn't somehow invalidate the cache before this
108
+ spy.call_count.should == mid_calls
109
+
110
+ user2 = ::User.find(user1.id)
111
+ user2.deleted.should == false
112
+ user2.deceased.should == false
113
+ user2.gender.should == 'male'
114
+ user2.donation_level.should == 7
115
+
116
+ (spy.call_count - mid_calls).should > 0
117
+ (spy.call_count - mid_calls).should <= 2
118
+ end
119
+ end
120
+
121
+ it "should be OK with manually-assigning an ID that's not in cache (that you somehow got out-of-band)" do
122
+ LowCardTables::Helpers::QuerySpyHelper.with_query_spy("lctables_spec_user_statuses") do |spy|
123
+ spy.call_count.should == 0
124
+
125
+ user1 = create_basic_user
126
+
127
+ mid_calls = spy.call_count
128
+ mid_calls.should > 0
129
+
130
+ new_status = ::UserStatusBackdoor.new
131
+ new_status.deleted = false
132
+ new_status.deceased = false
133
+ new_status.gender = 'male'
134
+ new_status.donation_level = 7
135
+ new_status.save!
136
+
137
+ user1.user_status_id = new_status.id
138
+ user1.deleted.should == false
139
+ user1.deceased.should == false
140
+ user1.gender.should == 'male'
141
+ user1.donation_level.should == 7
142
+
143
+ (spy.call_count - mid_calls).should > 0
144
+ (spy.call_count - mid_calls).should <= 2
145
+ end
146
+ end
147
+
148
+ context "with a cache listener" do
149
+ before :each do
150
+ class CacheListener
151
+ def initialize
152
+ @calls = [ ]
153
+ end
154
+
155
+ def call(name, started, finished, unique_id, data)
156
+ @calls << { :name => name, :started => started, :finished => finished, :unique_id => unique_id, :data => data }
157
+ end
158
+
159
+ def listen!(*event_names)
160
+ event_names.each do |event_name|
161
+ ActiveSupport::Notifications.subscribe(event_name, self)
162
+ end
163
+ end
164
+
165
+ def unlisten!
166
+ ActiveSupport::Notifications.unsubscribe(self)
167
+ end
168
+
169
+ attr_reader :calls
170
+ end
171
+
172
+ @cache_listener = CacheListener.new
173
+ @cache_listener.listen!('low_card_tables.cache_load', 'low_card_tables.cache_flush', 'low_card_tables.rows_created')
174
+ end
175
+
176
+ after :each do
177
+ @cache_listener.unlisten!
178
+ end
179
+
180
+ it "should notify listeners when flushing and loading its cache" do
181
+ @cache_listener.calls.length.should == 0
182
+
183
+ user1 = create_basic_user
184
+
185
+ call_count = @cache_listener.calls.length
186
+ call_count.should > 0
187
+ @cache_listener.calls.detect { |c| c[:name] == 'low_card_tables.cache_load' }.should be
188
+
189
+ start_time = Time.now
190
+ user1.deleted = true
191
+ user1.save!
192
+ end_time = Time.now
193
+
194
+ new_calls = @cache_listener.calls[call_count..-1]
195
+ new_calls.length.should > 0
196
+
197
+ flush_event = new_calls.detect { |c| c[:name] == 'low_card_tables.cache_flush' }
198
+ load_event = new_calls.detect { |c| c[:name] == 'low_card_tables.cache_load' }
199
+ flush_event.should be
200
+ load_event.should be
201
+
202
+ flush_event[:started].should >= start_time
203
+ flush_event[:finished].should >= flush_event[:started]
204
+ flush_event[:finished].should <= end_time
205
+ flush_event[:data][:reason].should == :creating_rows
206
+ flush_event[:data][:low_card_model].should == ::UserStatus
207
+
208
+ load_event[:started].should >= start_time
209
+ load_event[:started].should >= flush_event[:finished]
210
+ load_event[:finished].should >= load_event[:started]
211
+ load_event[:finished].should <= end_time
212
+ load_event[:data][:low_card_model].should == ::UserStatus
213
+ end
214
+
215
+ it "should notify listeners when the cache is manually flushed" do
216
+ user1 = create_basic_user
217
+ call_count = @cache_listener.calls.length
218
+
219
+ start_time = Time.now
220
+ ::UserStatus.low_card_flush_cache!
221
+ new_calls = @cache_listener.calls[call_count..-1]
222
+ end_time = Time.now
223
+
224
+ new_calls.length.should == 1
225
+ new_call = new_calls[0]
226
+ new_call[:name].should == 'low_card_tables.cache_flush'
227
+ new_call[:started].should >= start_time
228
+ new_call[:finished].should >= new_call[:started]
229
+ new_call[:finished].should <= end_time
230
+ new_call[:data][:reason].should == :manually_requested
231
+ new_call[:data][:low_card_model].should == ::UserStatus
232
+ end
233
+
234
+ it "should notify listeners when the cache is flushed because an ID was not found" do
235
+ user1 = create_basic_user
236
+ call_count = @cache_listener.calls.length
237
+
238
+ start_time = Time.now
239
+ lambda do
240
+ ::UserStatus.low_card_rows_for_ids([ user1.user_status_id, user1.user_status_id + 1000 ])
241
+ end.should raise_error(LowCardTables::Errors::LowCardIdNotFoundError)
242
+
243
+ new_calls = @cache_listener.calls[call_count..-1]
244
+ end_time = Time.now
245
+
246
+ new_calls.length.should > 1
247
+ new_call = new_calls.detect { |c| c[:name] == 'low_card_tables.cache_flush' && c[:data][:reason] == :id_not_found }
248
+ new_call[:name].should == 'low_card_tables.cache_flush'
249
+ new_call[:started].should >= start_time
250
+ new_call[:finished].should >= new_call[:started]
251
+ new_call[:finished].should <= end_time
252
+ new_call[:data][:reason].should == :id_not_found
253
+ new_call[:data][:low_card_model].should == ::UserStatus
254
+ end
255
+
256
+ it "should notify listeners when adding a new row" do
257
+ @cache_listener.calls.length.should == 0
258
+
259
+ user1 = create_basic_user
260
+
261
+ call_count = @cache_listener.calls.length
262
+ call_count.should > 0
263
+
264
+ start_time = Time.now
265
+ user1.gender = 'male'
266
+ user1.donation_level = 9
267
+ user1.save!
268
+ end_time = Time.now
269
+
270
+ new_calls = @cache_listener.calls[call_count..-1]
271
+ new_calls.length.should > 0
272
+
273
+ create_calls = new_calls.select { |c| c[:name] == 'low_card_tables.rows_created' }
274
+ create_calls.length.should == 1
275
+ create_call = create_calls[0]
276
+
277
+ create_call[:started].should >= start_time
278
+ create_call[:finished].should <= end_time
279
+ create_call[:finished].should >= create_call[:started]
280
+ create_call[:data][:low_card_model].should == ::UserStatus
281
+
282
+ keys = create_call[:data][:keys].map(&:to_s)
283
+ keys.sort.should == %w{deceased deleted donation_level gender}
284
+
285
+ values_array = create_call[:data][:values]
286
+ values_array.length.should == 1
287
+ values = values_array[0]
288
+ values.length.should == keys.length
289
+
290
+ values[keys.index('deceased')].should == false
291
+ values[keys.index('deleted')].should == false
292
+ values[keys.index('gender')].should == 'male'
293
+ values[keys.index('donation_level')].should == 9
294
+ end
295
+ end
296
+
297
+ context "cache policies" do
298
+ before :each do
299
+ class LowCardTables::LowCardTable::RowManager
300
+ def current_time
301
+ self.class.override_current_time || Time.now
302
+ end
303
+
304
+ class << self
305
+ def override_current_time=(x)
306
+ @override_current_time = x
307
+ end
308
+
309
+ def override_current_time
310
+ @override_current_time
311
+ end
312
+ end
313
+ end
314
+
315
+ module LowCardTables::LowCardTable::CacheExpiration::HasCacheExpiration::ClassMethods
316
+ def low_card_current_time
317
+ LowCardTables::LowCardTable::RowManager.override_current_time || Time.now
318
+ end
319
+ end
320
+
321
+ class LowCardTables::LowCardTable::Cache
322
+ def current_time
323
+ LowCardTables::LowCardTable::RowManager.override_current_time || Time.now
324
+ end
325
+ end
326
+
327
+ class LowCardTables::LowCardTable::CacheExpiration::ExponentialCacheExpirationPolicy
328
+ def current_time
329
+ LowCardTables::LowCardTable::RowManager.override_current_time || Time.now
330
+ end
331
+ end
332
+ end
333
+
334
+ def check_cache_expiration(*cache_expiration_settings, &block)
335
+ LowCardTables::Helpers::QuerySpyHelper.with_query_spy("lctables_spec_user_statuses") do |spy|
336
+ @start_time = Time.now
337
+ set_current_time(0)
338
+
339
+ ::UserStatus.low_card_cache_expiration *cache_expiration_settings
340
+
341
+ user1 = create_basic_user
342
+
343
+ @last_call_count = spy.call_count
344
+ @spy = spy
345
+
346
+ block.call
347
+ end
348
+ end
349
+
350
+ def set_current_time(x)
351
+ LowCardTables::LowCardTable::RowManager.override_current_time = @start_time + x
352
+ end
353
+
354
+ def time_and_check(time, cached_or_not)
355
+ set_current_time(time)
356
+ new_user = create_basic_user
357
+
358
+ if cached_or_not == :cached
359
+ @spy.call_count.should == @last_call_count
360
+ elsif cached_or_not == :uncached
361
+ @spy.call_count.should > @last_call_count
362
+ @last_call_count = @spy.call_count
363
+ else
364
+ raise "Unknown cached_or_not: #{cached_or_not.inspect}"
365
+ end
366
+ end
367
+
368
+ it "should inherit policies correctly" do
369
+ ::LowCardTables.low_card_cache_expiration 20
370
+
371
+ ::LowCardTables.low_card_cache_expiration.should == 20
372
+
373
+ klass = Class.new(::ActiveRecord::Base)
374
+ klass.class_eval { is_low_card_table }
375
+ klass.low_card_cache_expiration.should == 20
376
+
377
+ ::LowCardTables.low_card_cache_expiration :unlimited
378
+ klass.low_card_cache_expiration.should == :unlimited
379
+
380
+ klass.low_card_cache_expiration 40
381
+ klass.low_card_cache_expiration.should == 40
382
+
383
+ ::LowCardTables.low_card_cache_expiration 190
384
+ klass.low_card_cache_expiration.should == 40
385
+ end
386
+
387
+ it "should apply a fixed setting correctly" do
388
+ check_cache_expiration(2.minutes) do |spy, initial_call_count|
389
+ time_and_check(119.seconds, :cached)
390
+ time_and_check(121.seconds, :uncached)
391
+ time_and_check(240.seconds, :cached)
392
+ time_and_check(242.seconds, :uncached)
393
+ end
394
+ end
395
+
396
+ it "should apply a setting of 0 correctly" do
397
+ check_cache_expiration(0) do |spy, initial_call_count|
398
+ time_and_check(1.seconds, :uncached)
399
+ time_and_check(1.seconds, :uncached)
400
+ time_and_check(2.seconds, :uncached)
401
+ time_and_check(3.seconds, :uncached)
402
+ time_and_check(3.seconds, :uncached)
403
+ end
404
+ end
405
+
406
+ it "should apply a setting of :unlimited correctly" do
407
+ check_cache_expiration(:unlimited) do |spy, initial_call_count|
408
+ time_and_check(1.seconds, :cached)
409
+ time_and_check(2.seconds, :cached)
410
+ time_and_check(5.years, :cached)
411
+ end
412
+ end
413
+
414
+ it "should apply a setting of :exponential with default settings correctly" do
415
+ check_cache_expiration(:exponential) do |spy, initial_call_count|
416
+ # 0-180: zero floor
417
+ time_and_check(0.seconds, :uncached)
418
+ time_and_check(1.seconds, :uncached)
419
+ time_and_check(30.seconds, :uncached)
420
+ time_and_check(90.seconds, :uncached)
421
+ time_and_check(179.seconds, :uncached)
422
+
423
+ # 180-190: ten second minimum
424
+ time_and_check(181.seconds, :uncached)
425
+ time_and_check(182.seconds, :cached)
426
+ time_and_check(189.seconds, :cached)
427
+
428
+ # 190-210: first doubling (20 seconds)
429
+ time_and_check(191.seconds, :uncached)
430
+ time_and_check(192.seconds, :cached)
431
+ time_and_check(209.seconds, :cached)
432
+
433
+ # 210-250: second doubling (40 seconds)
434
+ # don't check here -- want to make sure we skip this properly
435
+
436
+ # 250-330: third doubling (80 seconds)
437
+ time_and_check(251.seconds, :uncached)
438
+ time_and_check(295.seconds, :cached)
439
+ time_and_check(329.seconds, :cached)
440
+
441
+ # 330-490: fourth doubling (160 seconds)
442
+ # 490-810: fifth doubling (320 seconds)
443
+ # 810-1450: sixth doubling (640 seconds)
444
+ # 1450-2730: seventh doubling (1280 seconds)
445
+ # 2730-5290: eighth doubling (2560 seconds)
446
+ time_and_check(2731.seconds, :uncached)
447
+ time_and_check(2732.seconds, :cached)
448
+ time_and_check(5289.seconds, :cached)
449
+
450
+ # 5290-8890: max (3600 seconds)
451
+ time_and_check(5291.seconds, :uncached)
452
+ time_and_check(5292.seconds, :cached)
453
+ time_and_check(8889.seconds, :cached)
454
+
455
+ # 8890-12490: max (3600 seconds)
456
+ time_and_check(8891.seconds, :uncached)
457
+ time_and_check(8892.seconds, :cached)
458
+ time_and_check(12489.seconds, :cached)
459
+
460
+ # 12490-16090: max (3600 seconds)
461
+ time_and_check(12491.seconds, :uncached)
462
+ time_and_check(16089.seconds, :cached)
463
+ end
464
+ end
465
+
466
+ it "should apply a setting of :exponential with custom settings correctly" do
467
+ check_cache_expiration(:exponential, :zero_floor_time => 10.seconds, :min_time => 5.seconds, :exponent => 3.0, :max_time => 200.seconds) do |spy, initial_call_count|
468
+ # 0-10: zero floor
469
+ time_and_check(0.seconds, :uncached)
470
+ time_and_check(1.seconds, :uncached)
471
+ time_and_check(9.seconds, :uncached)
472
+
473
+ # 10-15: five second minimum
474
+ time_and_check(10.seconds, :uncached)
475
+ time_and_check(14.seconds, :cached)
476
+
477
+ # 15-30: first tripling (15 seconds)
478
+ time_and_check(16.seconds, :uncached)
479
+ time_and_check(17.seconds, :cached)
480
+ time_and_check(29.seconds, :cached)
481
+
482
+ # 30-75: second tripling (45 seconds)
483
+ time_and_check(31.seconds, :uncached)
484
+ time_and_check(32.seconds, :cached)
485
+ time_and_check(74.seconds, :cached)
486
+
487
+ # 75-210: third tripling (135 seconds)
488
+
489
+ # 210-410: max (200 seconds)
490
+ time_and_check(211.seconds, :uncached)
491
+ time_and_check(212.seconds, :cached)
492
+ time_and_check(409.seconds, :cached)
493
+
494
+ # 410-610: max (200 seconds)
495
+ time_and_check(411.seconds, :uncached)
496
+ time_and_check(412.seconds, :cached)
497
+ time_and_check(609.seconds, :cached)
498
+ end
499
+ end
500
+
501
+ it "should apply a setting of :exponential with custom settings with no zero floor" do
502
+ check_cache_expiration(:exponential, :zero_floor_time => 0, :min_time => 5.seconds, :exponent => 3.0, :max_time => 200.seconds) do |spy, initial_call_count|
503
+ # 0-5: five second minimum
504
+ time_and_check(1.seconds, :cached)
505
+ time_and_check(4.seconds, :cached)
506
+
507
+ # 5-20: first tripling (15 seconds)
508
+ time_and_check(6.seconds, :uncached)
509
+ time_and_check(7.seconds, :cached)
510
+ time_and_check(19.seconds, :cached)
511
+
512
+ # 20-65: second tripling (45 seconds)
513
+ time_and_check(21.seconds, :uncached)
514
+ time_and_check(22.seconds, :cached)
515
+ time_and_check(64.seconds, :cached)
516
+
517
+ # 65-200: third tripling (135 seconds)
518
+
519
+ # 200-400: max (200 seconds)
520
+ time_and_check(201.seconds, :uncached)
521
+ time_and_check(202.seconds, :cached)
522
+ time_and_check(399.seconds, :cached)
523
+
524
+ # 400-600: max (200 seconds)
525
+ time_and_check(401.seconds, :uncached)
526
+ time_and_check(402.seconds, :cached)
527
+ time_and_check(599.seconds, :cached)
528
+ end
529
+ end
530
+ end
531
+ end