frivol 0.3.1 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +7 -6
- data/CHANGES.md +15 -0
- data/Gemfile +4 -1
- data/README.rdoc +56 -24
- data/Rakefile +17 -0
- data/VERSION +1 -1
- data/frivol.gemspec +19 -6
- data/lib/frivol.rb +1 -3
- data/lib/frivol/backend/multi.rb +159 -0
- data/lib/frivol/backend/redis.rb +93 -0
- data/lib/frivol/backend/redis_distributed.rb +51 -0
- data/lib/frivol/backend/riak.rb +186 -0
- data/lib/frivol/class_methods.rb +10 -4
- data/lib/frivol/config.rb +8 -22
- data/lib/frivol/helpers.rb +32 -34
- data/test/fake_redis.rb +44 -19
- data/test/helper.rb +116 -6
- data/test/test_backend.rb +33 -0
- data/test/test_counters.rb +31 -0
- data/test/test_frivol.rb +24 -9
- data/test/test_frivolize.rb +11 -11
- data/test/test_multi_backend.rb +127 -0
- data/test/test_multi_backend_counters.rb +113 -0
- data/test/test_multi_backend_expiry.rb +128 -0
- data/test/test_riak.rb +41 -0
- data/test/test_threads.rb +6 -1
- metadata +30 -6
data/test/fake_redis.rb
CHANGED
@@ -5,18 +5,19 @@ class Redis
|
|
5
5
|
@storage = {}
|
6
6
|
@expires = Hash.new(nil)
|
7
7
|
@version = config[:version] || "2.2.0"
|
8
|
+
@results = []
|
8
9
|
end
|
9
10
|
|
10
|
-
def
|
11
|
-
|
12
|
-
|
13
|
-
|
11
|
+
def get(key)
|
12
|
+
result = expired?(key) ? nil : @storage[key]
|
13
|
+
@results << result
|
14
|
+
result
|
14
15
|
end
|
15
16
|
|
16
|
-
def
|
17
|
-
# puts "store #{key} => #{value}"
|
17
|
+
def set(key, value)
|
18
18
|
@storage[key] = value
|
19
19
|
@expires.delete key
|
20
|
+
@results << 'OK'
|
20
21
|
end
|
21
22
|
|
22
23
|
def info(key)
|
@@ -24,33 +25,38 @@ class Redis
|
|
24
25
|
end
|
25
26
|
|
26
27
|
def del(key)
|
27
|
-
# puts "del #{key}"
|
28
28
|
@storage.delete key
|
29
29
|
@expires.delete key
|
30
|
+
@results << 1
|
31
|
+
1
|
30
32
|
end
|
31
33
|
|
32
34
|
def incr(key)
|
33
|
-
# puts "incr #{key}"
|
34
35
|
@storage[key] ||= 0
|
35
|
-
@storage[key] += 1
|
36
|
+
result = @storage[key] += 1
|
37
|
+
@results << result
|
38
|
+
result
|
36
39
|
end
|
37
40
|
|
38
41
|
def incrby(key, amount)
|
39
|
-
# puts "incr #{key}"
|
40
42
|
@storage[key] ||= 0
|
41
|
-
@storage[key] += amount
|
43
|
+
result = @storage[key] += amount
|
44
|
+
@results << result
|
45
|
+
result
|
42
46
|
end
|
43
47
|
|
44
48
|
def decr(key)
|
45
|
-
# puts "decr #{key}"
|
46
49
|
@storage[key] ||= 0
|
47
|
-
@storage[key] -= 1
|
50
|
+
result = @storage[key] -= 1
|
51
|
+
@results << result
|
52
|
+
result
|
48
53
|
end
|
49
54
|
|
50
55
|
def decrby(key, amount)
|
51
|
-
# puts "decr #{key}"
|
52
56
|
@storage[key] ||= 0
|
53
|
-
@storage[key] -= amount
|
57
|
+
result = @storage[key] -= amount
|
58
|
+
@results << result
|
59
|
+
result
|
54
60
|
end
|
55
61
|
|
56
62
|
def expire(key, time)
|
@@ -60,24 +66,38 @@ class Redis
|
|
60
66
|
raise RuntimeError.new("-ERR value is not an integer")
|
61
67
|
end
|
62
68
|
@expires[key] = Time.now + t
|
69
|
+
@results << 1
|
70
|
+
1
|
63
71
|
end
|
64
72
|
|
65
73
|
def exists(key)
|
66
|
-
@storage.key? key
|
74
|
+
result = @storage.key?(key) && !expired?(key)
|
75
|
+
@results << result
|
76
|
+
result
|
67
77
|
end
|
68
78
|
|
69
79
|
def ttl(key)
|
70
|
-
|
71
|
-
|
72
|
-
|
80
|
+
result = if expired?(key)
|
81
|
+
-2
|
82
|
+
elsif @expires[key].nil?
|
83
|
+
-1
|
84
|
+
else
|
85
|
+
(@expires[key] - Time.now).to_i
|
86
|
+
end
|
87
|
+
@results << result
|
88
|
+
result
|
73
89
|
end
|
74
90
|
|
75
91
|
def multi(&block)
|
92
|
+
@results = []
|
76
93
|
yield(self)
|
94
|
+
@results
|
77
95
|
end
|
78
96
|
|
79
97
|
def flushdb
|
80
98
|
@storage = {}
|
99
|
+
@results << 'OK'
|
100
|
+
'OK'
|
81
101
|
end
|
82
102
|
|
83
103
|
# Help with debugging
|
@@ -92,4 +112,9 @@ class Redis
|
|
92
112
|
def method_missing(meth, *args, &block)
|
93
113
|
puts "Missing method: #{meth} called with #{args.inspect}"
|
94
114
|
end
|
115
|
+
|
116
|
+
private
|
117
|
+
def expired?(key)
|
118
|
+
!@expires[key].nil? && @expires[key] < Time.now
|
119
|
+
end
|
95
120
|
end
|
data/test/helper.rb
CHANGED
@@ -7,14 +7,110 @@ require 'frivol'
|
|
7
7
|
|
8
8
|
class Test::Unit::TestCase
|
9
9
|
def setup
|
10
|
-
|
11
|
-
|
12
|
-
|
10
|
+
case ENV['backend']
|
11
|
+
when 'redis'
|
12
|
+
require 'frivol/backend/redis'
|
13
|
+
@backend = Frivol::Backend::Redis.new(:db => 10)
|
14
|
+
@backend.flushdb
|
15
|
+
Frivol::Config.backend = @backend
|
16
|
+
when 'redis_distributed'
|
17
|
+
require 'frivol/backend/redis_distributed'
|
18
|
+
@backend = Frivol::Backend::RedisDistributed.new([{:db => 11}, {:db => 12}])
|
19
|
+
@backend.flushdb
|
20
|
+
Frivol::Config.backend = @backend
|
21
|
+
when 'riak'
|
22
|
+
require 'frivol/backend/riak'
|
23
|
+
I18n.enforce_available_locales = false
|
24
|
+
Riak.disable_list_keys_warnings = true
|
25
|
+
@backend = Frivol::Backend::Riak.new(:nodes => [ { :host => '127.0.0.1' } ])
|
26
|
+
@backend.flushdb
|
27
|
+
Frivol::Config.backend = @backend
|
28
|
+
when 'multi'
|
29
|
+
require 'frivol/backend/redis'
|
30
|
+
fake_redis
|
31
|
+
require 'frivol/backend/multi'
|
32
|
+
@old_backend = Frivol::Backend::Redis.new(:db => 10)
|
33
|
+
@new_backend = Frivol::Backend::Redis.new(:db => 11)
|
34
|
+
@backend = Frivol::Backend::Multi.new([ @new_backend, @old_backend ])
|
35
|
+
@backend.flushdb
|
36
|
+
Frivol::Config.backend = @backend
|
37
|
+
when 'multi_redis_redis'
|
38
|
+
require 'frivol/backend/redis'
|
39
|
+
require 'frivol/backend/multi'
|
40
|
+
@old_backend = Frivol::Backend::Redis.new(:db => 10)
|
41
|
+
@new_backend = Frivol::Backend::Redis.new(:db => 11)
|
42
|
+
@backend = Frivol::Backend::Multi.new([ @new_backend, @old_backend ])
|
43
|
+
@backend.flushdb
|
44
|
+
Frivol::Config.backend = @backend
|
45
|
+
when 'multi_redis_redis_distributed'
|
46
|
+
require 'frivol/backend/redis'
|
47
|
+
require 'frivol/backend/redis_distributed'
|
48
|
+
require 'frivol/backend/multi'
|
49
|
+
@old_backend = Frivol::Backend::Redis.new(:db => 10)
|
50
|
+
@new_backend = Frivol::Backend::RedisDistributed.new(["redis://127.0.0.1:6379/11", "redis://127.0.0.1:6379/12"])
|
51
|
+
@backend = Frivol::Backend::Multi.new([ @new_backend, @old_backend ])
|
52
|
+
@backend.flushdb
|
53
|
+
Frivol::Config.backend = @backend
|
54
|
+
when 'multi_redis_riak'
|
55
|
+
require 'frivol/backend/redis'
|
56
|
+
require 'frivol/backend/riak'
|
57
|
+
require 'frivol/backend/multi'
|
58
|
+
I18n.enforce_available_locales = false
|
59
|
+
Riak.disable_list_keys_warnings = true
|
60
|
+
@old_backend = Frivol::Backend::Redis.new(:db => 10)
|
61
|
+
@new_backend = Frivol::Backend::Riak.new(:nodes => [ { :host => '127.0.0.1' } ])
|
62
|
+
@backend = Frivol::Backend::Multi.new([ @new_backend, @old_backend ])
|
63
|
+
@backend.flushdb
|
64
|
+
Frivol::Config.backend = @backend
|
65
|
+
when 'multi_redis_distributed_riak'
|
66
|
+
require 'frivol/backend/redis'
|
67
|
+
require 'frivol/backend/redis_distributed'
|
68
|
+
require 'frivol/backend/riak'
|
69
|
+
require 'frivol/backend/multi'
|
70
|
+
I18n.enforce_available_locales = false
|
71
|
+
Riak.disable_list_keys_warnings = true
|
72
|
+
@old_backend = Frivol::Backend::RedisDistributed.new(["redis://127.0.0.1:6379/11", "redis://127.0.0.1:6379/12"])
|
73
|
+
@new_backend = Frivol::Backend::Riak.new(:nodes => [ { :host => '127.0.0.1' } ])
|
74
|
+
@backend = Frivol::Backend::Multi.new([ @new_backend, @old_backend ])
|
75
|
+
@backend.flushdb
|
76
|
+
Frivol::Config.backend = @backend
|
77
|
+
when 'multi_riak_redis'
|
78
|
+
require 'frivol/backend/riak'
|
79
|
+
require 'frivol/backend/redis'
|
80
|
+
require 'frivol/backend/multi'
|
81
|
+
I18n.enforce_available_locales = false
|
82
|
+
Riak.disable_list_keys_warnings = true
|
83
|
+
@old_backend = Frivol::Backend::Riak.new(:nodes => [ { :host => '127.0.0.1' } ])
|
84
|
+
@new_backend = Frivol::Backend::Redis.new(:db => 10)
|
85
|
+
@backend = Frivol::Backend::Multi.new([ @new_backend, @old_backend ])
|
86
|
+
@backend.flushdb
|
87
|
+
Frivol::Config.backend = @backend
|
88
|
+
when 'multi_riak_redis_distributed'
|
89
|
+
require 'frivol/backend/riak'
|
90
|
+
require 'frivol/backend/redis'
|
91
|
+
require 'frivol/backend/redis_distributed'
|
92
|
+
require 'frivol/backend/multi'
|
93
|
+
I18n.enforce_available_locales = false
|
94
|
+
Riak.disable_list_keys_warnings = true
|
95
|
+
@old_backend = Frivol::Backend::Riak.new(:nodes => [ { :host => '127.0.0.1' } ])
|
96
|
+
@new_backend = Frivol::Backend::RedisDistributed.new(["redis://127.0.0.1:6379/11", "redis://127.0.0.1:6379/12"])
|
97
|
+
@backend = Frivol::Backend::Multi.new([ @new_backend, @old_backend ])
|
98
|
+
@backend.flushdb
|
99
|
+
Frivol::Config.backend = @backend
|
100
|
+
else
|
101
|
+
require 'frivol/backend/redis'
|
102
|
+
fake_redis
|
103
|
+
@backend = Frivol::Backend::Redis.new(:db => 10)
|
104
|
+
@backend.flushdb
|
105
|
+
Frivol::Config.backend = @backend
|
106
|
+
end
|
107
|
+
# NOTE: Because some backends like Riak are eventually consistent,
|
108
|
+
# we're changing the id of the test class per test.
|
109
|
+
@test_id = TestClass.incr_id
|
13
110
|
end
|
14
111
|
|
15
112
|
def teardown
|
16
|
-
|
17
|
-
Frivol::Config.redis.flushdb
|
113
|
+
@backend.flushdb
|
18
114
|
end
|
19
115
|
|
20
116
|
def fake_redis
|
@@ -24,12 +120,26 @@ class Test::Unit::TestCase
|
|
24
120
|
def ruby_one_eight?
|
25
121
|
@ruby_one_eight || `ruby -v`.include?('1.8')
|
26
122
|
end
|
123
|
+
|
124
|
+
def self.multi_test?
|
125
|
+
ENV['backend'].to_s.start_with?('multi')
|
126
|
+
end
|
127
|
+
|
128
|
+
def self.riak_test?
|
129
|
+
ENV['backend'].to_s.include?('riak')
|
130
|
+
end
|
27
131
|
end
|
28
132
|
|
29
133
|
class TestClass
|
30
134
|
include Frivol
|
31
135
|
|
136
|
+
@@id = 1
|
137
|
+
|
138
|
+
def self.incr_id
|
139
|
+
@@id += 1
|
140
|
+
end
|
141
|
+
|
32
142
|
def id
|
33
|
-
|
143
|
+
@@id
|
34
144
|
end
|
35
145
|
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require "#{File.expand_path(File.dirname(__FILE__))}/helper.rb"
|
2
|
+
|
3
|
+
class TestBackends < Test::Unit::TestCase
|
4
|
+
def test_ttl
|
5
|
+
t = TestClass.new
|
6
|
+
assert_nil @backend.ttl(t.storage_key)
|
7
|
+
|
8
|
+
t.store :something => 'somewhere'
|
9
|
+
assert_nil @backend.ttl(t.storage_key)
|
10
|
+
|
11
|
+
t.expire_storage 10
|
12
|
+
assert_in_delta 10, @backend.ttl(t.storage_key), 2
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_exists
|
16
|
+
t = TestClass.new
|
17
|
+
refute @backend.exists(t.storage_key)
|
18
|
+
|
19
|
+
t.store :something => 'somewhere'
|
20
|
+
assert @backend.exists(t.storage_key)
|
21
|
+
|
22
|
+
t.delete_storage
|
23
|
+
refute @backend.exists(t.storage_key)
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_exists_with_expiry
|
27
|
+
t = Class.new(TestClass) { storage_expires_in -1 }.new
|
28
|
+
refute @backend.exists(t.storage_key)
|
29
|
+
|
30
|
+
t.store :something => 'somewhere'
|
31
|
+
refute @backend.exists(t.storage_key)
|
32
|
+
end
|
33
|
+
end
|
data/test/test_counters.rb
CHANGED
@@ -69,4 +69,35 @@ class TestCounters < Test::Unit::TestCase
|
|
69
69
|
t.store_yellow 10
|
70
70
|
assert_equal 0, t.retrieve_yellow(0)
|
71
71
|
end
|
72
|
+
|
73
|
+
def test_expire_on_a_counter_bucket_using_increment
|
74
|
+
t = Class.new(TestClass) { storage_bucket :fuscia, :counter => true, :expires_in => -1 }.new
|
75
|
+
assert_equal 1, t.increment_fuscia
|
76
|
+
assert_equal 0, t.retrieve_fuscia(0)
|
77
|
+
end
|
78
|
+
|
79
|
+
def test_expire_on_a_counter_bucket_using_increment_by
|
80
|
+
t = Class.new(TestClass) { storage_bucket :magenta, :counter => true, :expires_in => -1 }.new
|
81
|
+
t.increment_magenta_by 10
|
82
|
+
assert_equal 0, t.retrieve_magenta(0)
|
83
|
+
end
|
84
|
+
|
85
|
+
def test_expire_on_a_counter_bucket_using_decrement
|
86
|
+
t = Class.new(TestClass) { storage_bucket :rose, :counter => true, :expires_in => -1 }.new
|
87
|
+
t.decrement_rose
|
88
|
+
assert_equal 0, t.retrieve_rose(0)
|
89
|
+
end
|
90
|
+
|
91
|
+
def test_expire_on_a_counter_bucket_using_decrement_by
|
92
|
+
t = Class.new(TestClass) { storage_bucket :orchid, :counter => true, :expires_in => -1 }.new
|
93
|
+
t.decrement_orchid_by 10
|
94
|
+
assert_equal 0, t.retrieve_orchid(0)
|
95
|
+
end
|
96
|
+
|
97
|
+
def test_delete_a_counter_bucket
|
98
|
+
t = Class.new(TestClass) { storage_bucket :debt, :counter => true }.new
|
99
|
+
t.store_debt 100_000
|
100
|
+
t.delete_debt
|
101
|
+
assert_equal 0, t.retrieve_debt(0)
|
102
|
+
end
|
72
103
|
end
|
data/test/test_frivol.rb
CHANGED
@@ -3,7 +3,7 @@ require "#{File.expand_path(File.dirname(__FILE__))}/helper.rb"
|
|
3
3
|
class TestFrivol < Test::Unit::TestCase
|
4
4
|
def test_have_a_default_storage_key_made_up_of_the_class_name_and_id
|
5
5
|
t = TestClass.new
|
6
|
-
assert_equal "TestClass
|
6
|
+
assert_equal "TestClass-#{@test_id}", t.storage_key
|
7
7
|
end
|
8
8
|
|
9
9
|
def test_store_and_retrieve_data
|
@@ -12,11 +12,27 @@ class TestFrivol < Test::Unit::TestCase
|
|
12
12
|
assert_equal "value", t.retrieve(:value => 'default')
|
13
13
|
end
|
14
14
|
|
15
|
+
def test_retrieve_non_existing
|
16
|
+
t = TestClass.new
|
17
|
+
assert_nothing_raised do
|
18
|
+
assert_nil t.retrieve(:nothing => nil)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
15
22
|
def test_return_a_default_for_a_value_thats_not_in_storage
|
16
23
|
t = TestClass.new
|
17
24
|
assert_equal "default", t.retrieve(:value => 'default')
|
18
25
|
end
|
19
26
|
|
27
|
+
def test_return_default_when_stored_value_is_emtpy_string
|
28
|
+
t = TestClass.new
|
29
|
+
key = t.storage_key
|
30
|
+
Frivol::Config.backend.set(key, "")
|
31
|
+
assert_nothing_raised do
|
32
|
+
assert_equal "36", t.retrieve( :some_string => "36")
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
20
36
|
def test_save_and_retrieve_multiple_values
|
21
37
|
t = TestClass.new
|
22
38
|
t.store :val1 => 1, :val2 => 2
|
@@ -68,7 +84,7 @@ class TestFrivol < Test::Unit::TestCase
|
|
68
84
|
end.new
|
69
85
|
|
70
86
|
t.store :value => 'value'
|
71
|
-
assert
|
87
|
+
assert @backend.get("my_storage")
|
72
88
|
end
|
73
89
|
|
74
90
|
def test_retain_Times_as_Times
|
@@ -87,7 +103,7 @@ class TestFrivol < Test::Unit::TestCase
|
|
87
103
|
end
|
88
104
|
|
89
105
|
def test_use_default_expiry_set_on_the_class
|
90
|
-
klass = Class.new(TestClass) { storage_expires_in -
|
106
|
+
klass = Class.new(TestClass) { storage_expires_in -10 }
|
91
107
|
t = klass.new
|
92
108
|
t.store :value => 'value'
|
93
109
|
|
@@ -101,10 +117,10 @@ class TestFrivol < Test::Unit::TestCase
|
|
101
117
|
t = Class.new(TestClass) { storage_expires_in 2 }.new
|
102
118
|
|
103
119
|
t.store :value => 'value'
|
104
|
-
assert
|
120
|
+
assert @backend.ttl(t.storage_key) > 0
|
105
121
|
|
106
122
|
t.store :value => 'value' # a second time
|
107
|
-
assert
|
123
|
+
assert @backend.ttl(t.storage_key) > 0
|
108
124
|
end
|
109
125
|
|
110
126
|
def test_be_able_to_include_in_other_classes_with_storage_expiry
|
@@ -119,8 +135,8 @@ class TestFrivol < Test::Unit::TestCase
|
|
119
135
|
t = TestClass.new
|
120
136
|
t.retrieve :value => 'default'
|
121
137
|
|
122
|
-
|
123
|
-
def
|
138
|
+
# ensure we're getting the result from the cache and not Redis
|
139
|
+
def @backend.get(key); raise 'Onoes, loaded again'; end
|
124
140
|
|
125
141
|
t.retrieve :value => 'default'
|
126
142
|
end
|
@@ -141,8 +157,7 @@ class TestFrivol < Test::Unit::TestCase
|
|
141
157
|
t.clear_gold
|
142
158
|
|
143
159
|
# ensure we're getting the result from Redis and not the cache
|
144
|
-
|
145
|
-
def redis.[](key)
|
160
|
+
def @backend.get(key, expiry = nil)
|
146
161
|
MultiJson.dump(:value => 'this is what we want')
|
147
162
|
end
|
148
163
|
|
data/test/test_frivolize.rb
CHANGED
@@ -33,18 +33,18 @@ class TestFrivolize < Test::Unit::TestCase
|
|
33
33
|
@@count = 0
|
34
34
|
|
35
35
|
# Imagine counting dinosuars takes a long time, what with the need to invent a time machine first and all
|
36
|
-
def
|
36
|
+
def dinosaurus_count
|
37
37
|
@@count += 1
|
38
38
|
end
|
39
|
-
frivolize :
|
39
|
+
frivolize :dinosaurus_count, { :bucket => :dinosauruss, :expires_in => -1 }
|
40
40
|
end
|
41
41
|
|
42
42
|
t = klass.new
|
43
|
-
assert_equal 1, t.
|
44
|
-
assert_equal 1, t.
|
43
|
+
assert_equal 1, t.dinosaurus_count
|
44
|
+
assert_equal 1, t.dinosaurus_count # Still 1 because it's coming from the class cache
|
45
45
|
|
46
46
|
t = klass.new
|
47
|
-
assert_equal 2, t.
|
47
|
+
assert_equal 2, t.dinosaurus_count
|
48
48
|
end
|
49
49
|
|
50
50
|
def test_frivolize_methods_with_expiry_as_a_counter
|
@@ -53,19 +53,19 @@ class TestFrivolize < Test::Unit::TestCase
|
|
53
53
|
@@count = 0
|
54
54
|
|
55
55
|
# Imagine counting dinosuars takes a long time, what with the need to invent a time machine first and all
|
56
|
-
def
|
56
|
+
def dinosaurii_count
|
57
57
|
@@count += 1
|
58
58
|
end
|
59
|
-
frivolize :
|
59
|
+
frivolize :dinosaurii_count, { :expires_in => -1, :counter => true }
|
60
60
|
end
|
61
61
|
|
62
62
|
t = klass.new
|
63
|
-
assert t.methods.include?(ruby_one_eight? ? '
|
63
|
+
assert t.methods.include?(ruby_one_eight? ? 'store_dinosaurii_count' : :store_dinosaurii_count) # check that the bucket name is the method name
|
64
64
|
|
65
|
-
assert_equal 1, t.
|
65
|
+
assert_equal 1, t.dinosaurii_count
|
66
66
|
|
67
67
|
t = klass.new # a fresh instance after value expired
|
68
|
-
assert_equal 2, t.
|
68
|
+
assert_equal 2, t.dinosaurii_count
|
69
69
|
end
|
70
70
|
|
71
71
|
def test_frivolize_with_seed_as_a_counter_for_increment
|
@@ -73,7 +73,7 @@ class TestFrivolize < Test::Unit::TestCase
|
|
73
73
|
def bak_baks
|
74
74
|
88_888
|
75
75
|
end
|
76
|
-
frivolize :bak_baks, :counter => true, :seed => Proc.new{ |obj| obj.bak_baks}
|
76
|
+
frivolize :bak_baks, :counter => true, :seed => Proc.new{ |obj| obj.bak_baks }
|
77
77
|
end.new
|
78
78
|
|
79
79
|
assert_equal 88_888 + 1, t.increment_bak_baks
|