frivol 0.1.7 → 0.2.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.
- data/VERSION +1 -1
- data/frivol.gemspec +2 -2
- data/lib/frivol.rb +70 -13
- data/test/fake_redis.rb +31 -5
- data/test/test_frivol.rb +100 -18
- metadata +5 -5
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.2.0
|
data/frivol.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{frivol}
|
8
|
-
s.version = "0.
|
8
|
+
s.version = "0.2.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Marc Heiligers"]
|
12
|
-
s.date = %q{
|
12
|
+
s.date = %q{2011-03-17}
|
13
13
|
s.description = %q{Simple Redis backed temporary storage intended primarily for use with ActiveRecord models to provide caching}
|
14
14
|
s.email = %q{marc@eternal.co.za}
|
15
15
|
s.extra_rdoc_files = [
|
data/lib/frivol.rb
CHANGED
@@ -188,17 +188,24 @@ module Frivol
|
|
188
188
|
data, is_new = get_data_and_is_new instance
|
189
189
|
data[bucket.to_s] = hash
|
190
190
|
|
191
|
-
|
192
|
-
Frivol::Config.redis[key] = hash.to_json
|
193
|
-
|
194
|
-
if is_new[bucket.to_s]
|
195
|
-
time = instance.class.storage_expiry(bucket)
|
196
|
-
Frivol::Config.redis.expire(key, time) if time != Frivol::NEVER_EXPIRE
|
197
|
-
is_new[bucket.to_s] = false
|
198
|
-
end
|
191
|
+
store_value instance, is_new[bucket.to_s], hash.to_json, bucket
|
199
192
|
|
200
193
|
self.set_data_and_is_new instance, data, is_new
|
201
194
|
end
|
195
|
+
|
196
|
+
def self.store_value(instance, is_new, value, bucket = nil)
|
197
|
+
key = instance.send(:storage_key, bucket)
|
198
|
+
time = instance.class.storage_expiry(bucket)
|
199
|
+
if time == Frivol::NEVER_EXPIRE
|
200
|
+
Frivol::Config.redis[key] = value
|
201
|
+
else
|
202
|
+
Frivol::Config.redis.multi do |redis|
|
203
|
+
time = redis.ttl(key) unless is_new
|
204
|
+
redis[key] = value
|
205
|
+
redis.expire(key, time)
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
202
209
|
|
203
210
|
def self.retrieve_hash(instance, bucket = nil)
|
204
211
|
data, is_new = get_data_and_is_new instance
|
@@ -242,11 +249,7 @@ module Frivol
|
|
242
249
|
def self.store_counter(instance, counter, value)
|
243
250
|
key = instance.send(:storage_key, counter)
|
244
251
|
is_new = !Frivol::Config.redis.exists(key)
|
245
|
-
|
246
|
-
if is_new
|
247
|
-
time = instance.class.storage_expiry(counter)
|
248
|
-
Frivol::Config.redis.expire(key, time) if time != Frivol::NEVER_EXPIRE
|
249
|
-
end
|
252
|
+
store_value instance, is_new, value, counter
|
250
253
|
end
|
251
254
|
|
252
255
|
def self.retrieve_counter(instance, counter, default)
|
@@ -339,6 +342,60 @@ module Frivol
|
|
339
342
|
Frivol::Helpers.clear_hash(self, bucket)
|
340
343
|
end
|
341
344
|
end
|
345
|
+
|
346
|
+
# Use Frivol to cache results for a method (similar to memoize).
|
347
|
+
# Options are :bucket which sets the bucket name for the storage,
|
348
|
+
# :expires_in which sets the expiry time for a bucket,
|
349
|
+
# and :counter to create a special counter storage bucket.
|
350
|
+
#
|
351
|
+
# If not :counter the key is the method_name.
|
352
|
+
#
|
353
|
+
# If you supply :expires_in you must also supply a :bucket otherwise
|
354
|
+
# it is ignored (and the default class expires_in is used if supplied).
|
355
|
+
#
|
356
|
+
# If :counter and no :bucket is provided the :bucket is set to the
|
357
|
+
# :bucket is set to the method_name (and so the :expires_in will be used).
|
358
|
+
def frivolize(method_name, options = {})
|
359
|
+
bucket = options[:bucket]
|
360
|
+
time = options[:expires_in]
|
361
|
+
is_counter = options[:counter]
|
362
|
+
bucket = method_name if bucket.nil? && is_counter
|
363
|
+
frivolized_method_name = "frivolized_#{method_name}"
|
364
|
+
|
365
|
+
self.class_eval do
|
366
|
+
alias_method frivolized_method_name, method_name
|
367
|
+
storage_bucket(bucket, { :expires_in => time, :counter => is_counter }) unless bucket.nil?
|
368
|
+
|
369
|
+
if is_counter
|
370
|
+
define_method method_name do
|
371
|
+
value = send "retrieve_#{bucket}", -2147483647 # A rediculously small number that is unlikely to be used: -2**31 + 1
|
372
|
+
if value == -2147483647
|
373
|
+
value = send frivolized_method_name
|
374
|
+
send "store_#{bucket}", value
|
375
|
+
end
|
376
|
+
value
|
377
|
+
end
|
378
|
+
elsif !bucket.nil?
|
379
|
+
define_method method_name do
|
380
|
+
value = send "retrieve_#{bucket}", { method_name => false }
|
381
|
+
if !value
|
382
|
+
value = send frivolized_method_name
|
383
|
+
send "store_#{bucket}", { method_name => value }
|
384
|
+
end
|
385
|
+
value
|
386
|
+
end
|
387
|
+
else
|
388
|
+
define_method method_name do
|
389
|
+
value = retrieve method_name => false
|
390
|
+
if !value
|
391
|
+
value = send frivolized_method_name
|
392
|
+
store method_name.to_sym => value
|
393
|
+
end
|
394
|
+
value
|
395
|
+
end
|
396
|
+
end
|
397
|
+
end
|
398
|
+
end
|
342
399
|
end
|
343
400
|
|
344
401
|
# def storage_default(keys_and_defaults)
|
data/test/fake_redis.rb
CHANGED
@@ -5,35 +5,61 @@ class Redis
|
|
5
5
|
end
|
6
6
|
|
7
7
|
def [](key)
|
8
|
-
# puts "retrieve #{key}"
|
8
|
+
# puts "retrieve #{key} => #{@storage[key]} (#{(!@expires[key].nil? && @expires[key] < Time.now) ? 'expired' : 'alive'})"
|
9
9
|
return nil if !@expires[key].nil? && @expires[key] < Time.now
|
10
10
|
@storage[key]
|
11
11
|
end
|
12
12
|
|
13
13
|
def []=(key, value)
|
14
|
-
# puts "store #{key}"
|
14
|
+
# puts "store #{key} => #{value}"
|
15
15
|
@storage[key] = value
|
16
|
+
@expires.delete key
|
16
17
|
end
|
17
18
|
|
18
19
|
def del(key)
|
19
20
|
# puts "del #{key}"
|
20
|
-
@storage
|
21
|
-
@expires
|
21
|
+
@storage.delete key
|
22
|
+
@expires.delete key
|
22
23
|
end
|
23
24
|
|
24
25
|
def incr(key)
|
26
|
+
# puts "incr #{key}"
|
25
27
|
@storage[key] += 1
|
26
28
|
end
|
27
29
|
|
28
30
|
def expire(key, time)
|
29
|
-
|
31
|
+
begin
|
32
|
+
t = Integer(time)
|
33
|
+
rescue
|
34
|
+
raise RuntimeError.new("-ERR value is not an integer")
|
35
|
+
end
|
36
|
+
@expires[key] = Time.now + t
|
30
37
|
end
|
31
38
|
|
32
39
|
def exists(key)
|
33
40
|
@storage.key? key
|
34
41
|
end
|
35
42
|
|
43
|
+
def ttl(key)
|
44
|
+
# puts "ttl #{key}"
|
45
|
+
return -1 if @expires[key].nil? || @expires[key] < Time.now
|
46
|
+
(@expires[key] - Time.now).to_i
|
47
|
+
end
|
48
|
+
|
49
|
+
def multi(&block)
|
50
|
+
yield(self)
|
51
|
+
end
|
52
|
+
|
36
53
|
def flushdb
|
37
54
|
@storage = {}
|
38
55
|
end
|
56
|
+
|
57
|
+
# Help with debugging
|
58
|
+
def inspect
|
59
|
+
@storage.keys.sort.map do |key|
|
60
|
+
result = "\n#{key} => #{@storage[key].inspect}"
|
61
|
+
result << "\n\texpires at #{@expires[key]}" if @expires.key?(key)
|
62
|
+
result
|
63
|
+
end
|
64
|
+
end
|
39
65
|
end
|
data/test/test_frivol.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require "#{File.expand_path(File.dirname(__FILE__))}/helper.rb"
|
2
2
|
|
3
3
|
class TestFrivol < Test::Unit::TestCase
|
4
4
|
def setup
|
@@ -8,6 +8,7 @@ class TestFrivol < Test::Unit::TestCase
|
|
8
8
|
end
|
9
9
|
|
10
10
|
def teardown
|
11
|
+
# puts Frivol::Config.redis.inspect
|
11
12
|
Frivol::Config.redis.flushdb
|
12
13
|
end
|
13
14
|
|
@@ -114,38 +115,52 @@ class TestFrivol < Test::Unit::TestCase
|
|
114
115
|
assert_equal Time.local(2010, 8, 20), t.load
|
115
116
|
end
|
116
117
|
|
117
|
-
should "expire storage the
|
118
|
+
should "expire storage the everytime time it's stored" do
|
118
119
|
class ExpiryTestClass < TestClass
|
119
120
|
storage_expires_in 60
|
120
121
|
end
|
121
122
|
|
122
|
-
Frivol::Config.redis.expects(:expire).
|
123
|
+
Frivol::Config.redis.expects(:expire).twice
|
124
|
+
Frivol::Config.redis.expects(:ttl).once
|
123
125
|
t = ExpiryTestClass.new
|
124
126
|
t.load
|
125
127
|
t.save
|
128
|
+
t = ExpiryTestClass.new # get a new one
|
126
129
|
t.save
|
127
130
|
end
|
128
131
|
|
129
132
|
should "expires should not throw nasty errors" do
|
130
133
|
t = TestClass.new
|
131
134
|
t.save
|
132
|
-
t.expire_storage
|
133
|
-
sleep
|
135
|
+
t.expire_storage 1
|
136
|
+
sleep 2
|
134
137
|
t = TestClass.new # Get a fresh instance so that the @frivol_data is empty
|
135
138
|
assert_equal "default", t.load
|
136
139
|
end
|
137
140
|
|
138
141
|
should "use default expiry set on the class" do
|
139
142
|
class ExpiryTestClass < TestClass
|
140
|
-
storage_expires_in
|
143
|
+
storage_expires_in 1
|
141
144
|
end
|
142
145
|
t = ExpiryTestClass.new
|
143
146
|
t.save
|
144
|
-
sleep
|
145
|
-
t =
|
147
|
+
sleep 2
|
148
|
+
t = ExpiryTestClass.new # Get a fresh instance so that the @frivol_data is empty
|
146
149
|
assert_equal "default", t.load
|
147
150
|
end
|
148
151
|
|
152
|
+
# If you save a value to a volatile key, the expiry is cleared
|
153
|
+
should "resaving a value should not clear the expiry" do
|
154
|
+
class ResavingExpiryTestClass < TestClass
|
155
|
+
storage_expires_in 2
|
156
|
+
end
|
157
|
+
t = ResavingExpiryTestClass.new
|
158
|
+
t.save
|
159
|
+
assert Frivol::Config.redis.ttl(t.storage_key) > 0
|
160
|
+
t.save # a second time
|
161
|
+
assert Frivol::Config.redis.ttl(t.storage_key) > 0
|
162
|
+
end
|
163
|
+
|
149
164
|
should "be able to include in other classes with storage expiry" do
|
150
165
|
class BlankTestClass
|
151
166
|
end
|
@@ -199,18 +214,18 @@ class TestFrivol < Test::Unit::TestCase
|
|
199
214
|
|
200
215
|
should "have different expiry times for different buckets" do
|
201
216
|
class ExpireBucketsTestClass < TestClass
|
202
|
-
storage_bucket :blue, :expires_in =>
|
217
|
+
storage_bucket :blue, :expires_in => 1
|
203
218
|
storage_expires_in 2
|
204
219
|
end
|
205
220
|
t = ExpireBucketsTestClass.new
|
206
|
-
assert_equal
|
221
|
+
assert_equal 1, ExpireBucketsTestClass.storage_expiry(:blue)
|
207
222
|
assert_equal 2, ExpireBucketsTestClass.storage_expiry
|
208
223
|
end
|
209
224
|
|
210
225
|
should "expire data in buckets" do
|
211
226
|
class ExpireBucketsTestClass < TestClass
|
212
|
-
storage_bucket :blue, :expires_in =>
|
213
|
-
storage_expires_in
|
227
|
+
storage_bucket :blue, :expires_in => 1
|
228
|
+
storage_expires_in 3
|
214
229
|
|
215
230
|
def save_blue
|
216
231
|
store_blue :value => "blue value"
|
@@ -223,7 +238,7 @@ class TestFrivol < Test::Unit::TestCase
|
|
223
238
|
t = ExpireBucketsTestClass.new
|
224
239
|
t.save
|
225
240
|
t.save_blue
|
226
|
-
sleep
|
241
|
+
sleep 2
|
227
242
|
t = ExpireBucketsTestClass.new # get a new instance so @frivol_data is empty
|
228
243
|
assert_equal "value", t.load
|
229
244
|
assert_equal "blue default", t.load_blue
|
@@ -261,15 +276,15 @@ class TestFrivol < Test::Unit::TestCase
|
|
261
276
|
|
262
277
|
should "set expiry on counters" do
|
263
278
|
class SetExpireCounterTestClass < TestClass
|
264
|
-
storage_bucket :sheep_counter, :counter => true, :expires_in =>
|
279
|
+
storage_bucket :sheep_counter, :counter => true, :expires_in => 1
|
265
280
|
end
|
266
281
|
t = SetExpireCounterTestClass.new
|
267
|
-
assert_equal
|
282
|
+
assert_equal 1, SetExpireCounterTestClass.storage_expiry(:sheep_counter)
|
268
283
|
end
|
269
284
|
|
270
285
|
should "expire a counter bucket" do
|
271
286
|
class ExpireCounterTestClass < TestClass
|
272
|
-
storage_bucket :kitten_grave, :counter => true, :expires_in =>
|
287
|
+
storage_bucket :kitten_grave, :counter => true, :expires_in => 1
|
273
288
|
|
274
289
|
def bury_kittens
|
275
290
|
store_kitten_grave 10
|
@@ -279,12 +294,13 @@ class TestFrivol < Test::Unit::TestCase
|
|
279
294
|
retrieve_kitten_grave 0
|
280
295
|
end
|
281
296
|
end
|
297
|
+
|
282
298
|
k = ExpireCounterTestClass.new
|
283
299
|
k.bury_kittens
|
284
300
|
assert_equal 10, k.peek_in_grave
|
285
|
-
sleep(0.6)
|
286
|
-
assert_equal 0, k.peek_in_grave
|
287
301
|
|
302
|
+
sleep 2
|
303
|
+
assert_equal 0, k.peek_in_grave
|
288
304
|
end
|
289
305
|
|
290
306
|
# Note: this test will fail from time to time using fake_redis because fake_redis is not thread safe
|
@@ -359,5 +375,71 @@ class TestFrivol < Test::Unit::TestCase
|
|
359
375
|
assert_equal "value", t.load_gold
|
360
376
|
end
|
361
377
|
|
378
|
+
should "frivolize methods" do
|
379
|
+
class FrivolizeTestClass < TestClass
|
380
|
+
def dinosaur_count
|
381
|
+
10
|
382
|
+
end
|
383
|
+
frivolize :dinosaur_count
|
384
|
+
end
|
385
|
+
|
386
|
+
Frivol::Config.redis.expects(:[]=).once
|
387
|
+
Frivol::Config.redis.expects(:[]).twice.returns(nil, { :dinosaur_count => 10 }.to_json)
|
388
|
+
|
389
|
+
t = FrivolizeTestClass.new
|
390
|
+
assert t.methods.include? "dinosaur_count"
|
391
|
+
assert t.methods.include? "frivolized_dinosaur_count"
|
392
|
+
|
393
|
+
assert_equal 10, t.dinosaur_count
|
394
|
+
|
395
|
+
t = FrivolizeTestClass.new # a fresh instance
|
396
|
+
assert_equal 10, t.dinosaur_count
|
397
|
+
end
|
398
|
+
|
399
|
+
should "frivolize methods with expiry in a bucket" do
|
400
|
+
class FrivolizeExpiringBucketTestClass < TestClass
|
401
|
+
def dinosaur_count
|
402
|
+
10
|
403
|
+
end
|
404
|
+
frivolize :dinosaur_count, { :bucket => :dinosaurs, :expires_in => 1 }
|
405
|
+
end
|
406
|
+
Frivol::Config.redis.expects(:[]=).twice
|
407
|
+
Frivol::Config.redis.expects(:[]).times(3).returns(nil, { :dinosaur_count => 10 }.to_json, nil)
|
408
|
+
|
409
|
+
t = FrivolizeExpiringBucketTestClass.new
|
410
|
+
assert_equal 10, t.dinosaur_count
|
411
|
+
|
412
|
+
t = FrivolizeExpiringBucketTestClass.new # a fresh instance
|
413
|
+
assert_equal 10, t.dinosaur_count
|
414
|
+
|
415
|
+
sleep 2
|
416
|
+
|
417
|
+
t = FrivolizeExpiringBucketTestClass.new # another fresh instance after value expired
|
418
|
+
assert_equal 10, t.dinosaur_count
|
419
|
+
end
|
420
|
+
|
421
|
+
should "frivolize methods with expiry as a counter" do
|
422
|
+
class FrivolizeExpiringBucketTestClass < TestClass
|
423
|
+
def dinosaur_count
|
424
|
+
10
|
425
|
+
end
|
426
|
+
frivolize :dinosaur_count, { :expires_in => 1, :counter => true }
|
427
|
+
end
|
428
|
+
Frivol::Config.redis.expects(:[]=).twice
|
429
|
+
Frivol::Config.redis.expects(:[]).times(3).returns(nil, 10, nil)
|
430
|
+
|
431
|
+
t = FrivolizeExpiringBucketTestClass.new
|
432
|
+
assert t.methods.include? "store_dinosaur_count" # check that the bucket name is the method name
|
433
|
+
|
434
|
+
assert_equal 10, t.dinosaur_count
|
435
|
+
|
436
|
+
t = FrivolizeExpiringBucketTestClass.new # a fresh instance
|
437
|
+
assert_equal 10, t.dinosaur_count
|
438
|
+
|
439
|
+
sleep 2
|
440
|
+
|
441
|
+
t = FrivolizeExpiringBucketTestClass.new # another fresh instance after value expired
|
442
|
+
assert_equal 10, t.dinosaur_count
|
443
|
+
end
|
362
444
|
|
363
445
|
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: frivol
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 23
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
-
|
9
|
-
-
|
10
|
-
version: 0.
|
8
|
+
- 2
|
9
|
+
- 0
|
10
|
+
version: 0.2.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Marc Heiligers
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date:
|
18
|
+
date: 2011-03-17 00:00:00 +02:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|