modesty 0.1.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/Gemfile +13 -0
- data/Gemfile.lock +18 -0
- data/LICENSE +21 -0
- data/README.md +121 -0
- data/Rakefile +29 -0
- data/VERSION +1 -0
- data/init.rb +1 -0
- data/lib/modesty.rb +26 -0
- data/lib/modesty/api.rb +14 -0
- data/lib/modesty/core_ext.rb +5 -0
- data/lib/modesty/core_ext/array.rb +21 -0
- data/lib/modesty/core_ext/fixnum.rb +5 -0
- data/lib/modesty/core_ext/hash.rb +39 -0
- data/lib/modesty/core_ext/string.rb +9 -0
- data/lib/modesty/core_ext/symbol.rb +33 -0
- data/lib/modesty/datastore.rb +51 -0
- data/lib/modesty/datastore/redis.rb +180 -0
- data/lib/modesty/experiment.rb +87 -0
- data/lib/modesty/experiment/base.rb +47 -0
- data/lib/modesty/experiment/builder.rb +48 -0
- data/lib/modesty/experiment/console.rb +4 -0
- data/lib/modesty/experiment/data.rb +75 -0
- data/lib/modesty/experiment/interface.rb +29 -0
- data/lib/modesty/experiment/significance.rb +376 -0
- data/lib/modesty/experiment/stats.rb +163 -0
- data/lib/modesty/frameworks/rails.rb +27 -0
- data/lib/modesty/identity.rb +32 -0
- data/lib/modesty/load.rb +80 -0
- data/lib/modesty/load/load_experiments.rb +14 -0
- data/lib/modesty/load/load_metrics.rb +17 -0
- data/lib/modesty/metric.rb +56 -0
- data/lib/modesty/metric/base.rb +38 -0
- data/lib/modesty/metric/builder.rb +23 -0
- data/lib/modesty/metric/data.rb +133 -0
- data/modesty.gemspec +192 -0
- data/spec/core_ext_spec.rb +17 -0
- data/spec/experiment_spec.rb +239 -0
- data/spec/identity_spec.rb +161 -0
- data/spec/load_spec.rb +87 -0
- data/spec/metric_spec.rb +176 -0
- data/spec/rails_spec.rb +48 -0
- data/spec/redis_spec.rb +29 -0
- data/spec/significance_spec.rb +147 -0
- data/spec/spec.opts +1 -0
- data/test/myapp/config/modesty.yml +9 -0
- data/test/myapp/modesty/experiments/cookbook.rb +4 -0
- data/test/myapp/modesty/metrics/kitchen_metrics.rb +9 -0
- data/test/myapp/modesty/metrics/stove/burner_metrics.rb +2 -0
- data/vendor/.piston.yml +8 -0
- data/vendor/mock_redis/.gitignore +2 -0
- data/vendor/mock_redis/README +8 -0
- data/vendor/mock_redis/lib/mock_redis.rb +10 -0
- data/vendor/mock_redis/lib/mock_redis/hash.rb +61 -0
- data/vendor/mock_redis/lib/mock_redis/list.rb +6 -0
- data/vendor/mock_redis/lib/mock_redis/misc.rb +69 -0
- data/vendor/mock_redis/lib/mock_redis/set.rb +108 -0
- data/vendor/mock_redis/lib/mock_redis/string.rb +32 -0
- data/vendor/redis-rb/.gitignore +8 -0
- data/vendor/redis-rb/LICENSE +20 -0
- data/vendor/redis-rb/README.markdown +129 -0
- data/vendor/redis-rb/Rakefile +155 -0
- data/vendor/redis-rb/benchmarking/logging.rb +62 -0
- data/vendor/redis-rb/benchmarking/pipeline.rb +51 -0
- data/vendor/redis-rb/benchmarking/speed.rb +21 -0
- data/vendor/redis-rb/benchmarking/suite.rb +24 -0
- data/vendor/redis-rb/benchmarking/thread_safety.rb +38 -0
- data/vendor/redis-rb/benchmarking/worker.rb +71 -0
- data/vendor/redis-rb/examples/basic.rb +15 -0
- data/vendor/redis-rb/examples/dist_redis.rb +43 -0
- data/vendor/redis-rb/examples/incr-decr.rb +17 -0
- data/vendor/redis-rb/examples/list.rb +26 -0
- data/vendor/redis-rb/examples/pubsub.rb +31 -0
- data/vendor/redis-rb/examples/sets.rb +36 -0
- data/vendor/redis-rb/examples/unicorn/config.ru +3 -0
- data/vendor/redis-rb/examples/unicorn/unicorn.rb +20 -0
- data/vendor/redis-rb/lib/redis.rb +676 -0
- data/vendor/redis-rb/lib/redis/client.rb +201 -0
- data/vendor/redis-rb/lib/redis/compat.rb +21 -0
- data/vendor/redis-rb/lib/redis/connection.rb +134 -0
- data/vendor/redis-rb/lib/redis/distributed.rb +526 -0
- data/vendor/redis-rb/lib/redis/hash_ring.rb +131 -0
- data/vendor/redis-rb/lib/redis/pipeline.rb +13 -0
- data/vendor/redis-rb/lib/redis/subscribe.rb +79 -0
- data/vendor/redis-rb/redis.gemspec +29 -0
- data/vendor/redis-rb/test/commands_on_hashes_test.rb +46 -0
- data/vendor/redis-rb/test/commands_on_lists_test.rb +50 -0
- data/vendor/redis-rb/test/commands_on_sets_test.rb +78 -0
- data/vendor/redis-rb/test/commands_on_sorted_sets_test.rb +109 -0
- data/vendor/redis-rb/test/commands_on_strings_test.rb +70 -0
- data/vendor/redis-rb/test/commands_on_value_types_test.rb +88 -0
- data/vendor/redis-rb/test/connection_handling_test.rb +87 -0
- data/vendor/redis-rb/test/db/.gitignore +1 -0
- data/vendor/redis-rb/test/distributd_key_tags_test.rb +53 -0
- data/vendor/redis-rb/test/distributed_blocking_commands_test.rb +54 -0
- data/vendor/redis-rb/test/distributed_commands_on_hashes_test.rb +12 -0
- data/vendor/redis-rb/test/distributed_commands_on_lists_test.rb +18 -0
- data/vendor/redis-rb/test/distributed_commands_on_sets_test.rb +85 -0
- data/vendor/redis-rb/test/distributed_commands_on_strings_test.rb +50 -0
- data/vendor/redis-rb/test/distributed_commands_on_value_types_test.rb +73 -0
- data/vendor/redis-rb/test/distributed_commands_requiring_clustering_test.rb +141 -0
- data/vendor/redis-rb/test/distributed_connection_handling_test.rb +25 -0
- data/vendor/redis-rb/test/distributed_internals_test.rb +18 -0
- data/vendor/redis-rb/test/distributed_persistence_control_commands_test.rb +24 -0
- data/vendor/redis-rb/test/distributed_publish_subscribe_test.rb +90 -0
- data/vendor/redis-rb/test/distributed_remote_server_control_commands_test.rb +31 -0
- data/vendor/redis-rb/test/distributed_sorting_test.rb +21 -0
- data/vendor/redis-rb/test/distributed_test.rb +60 -0
- data/vendor/redis-rb/test/distributed_transactions_test.rb +34 -0
- data/vendor/redis-rb/test/encoding_test.rb +16 -0
- data/vendor/redis-rb/test/helper.rb +86 -0
- data/vendor/redis-rb/test/internals_test.rb +27 -0
- data/vendor/redis-rb/test/lint/hashes.rb +90 -0
- data/vendor/redis-rb/test/lint/internals.rb +53 -0
- data/vendor/redis-rb/test/lint/lists.rb +93 -0
- data/vendor/redis-rb/test/lint/sets.rb +66 -0
- data/vendor/redis-rb/test/lint/sorted_sets.rb +132 -0
- data/vendor/redis-rb/test/lint/strings.rb +98 -0
- data/vendor/redis-rb/test/lint/value_types.rb +84 -0
- data/vendor/redis-rb/test/persistence_control_commands_test.rb +22 -0
- data/vendor/redis-rb/test/pipelining_commands_test.rb +78 -0
- data/vendor/redis-rb/test/publish_subscribe_test.rb +151 -0
- data/vendor/redis-rb/test/redis_mock.rb +64 -0
- data/vendor/redis-rb/test/remote_server_control_commands_test.rb +56 -0
- data/vendor/redis-rb/test/sorting_test.rb +44 -0
- data/vendor/redis-rb/test/test.conf +8 -0
- data/vendor/redis-rb/test/thread_safety_test.rb +34 -0
- data/vendor/redis-rb/test/transactions_test.rb +91 -0
- data/vendor/redis-rb/test/unknown_commands_test.rb +14 -0
- data/vendor/redis-rb/test/url_param_test.rb +52 -0
- metadata +277 -0
data/spec/redis_spec.rb
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
require 'modesty'
|
|
2
|
+
|
|
3
|
+
describe "Real Redis" do
|
|
4
|
+
before :each do
|
|
5
|
+
Modesty.metrics.clear
|
|
6
|
+
Modesty.experiments.clear
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
it "can connect to redis" do
|
|
10
|
+
lambda { Modesty.set_store :redis }.should_not raise_error
|
|
11
|
+
Modesty.data.store.should be_an_instance_of Redis
|
|
12
|
+
lambda { Modesty.data.flushdb }.should_not raise_error
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
it "can track metrics in real redis" do
|
|
16
|
+
Modesty.new_metric :foo
|
|
17
|
+
lambda do
|
|
18
|
+
(1..100).each do |i|
|
|
19
|
+
Modesty.track! :foo, 2
|
|
20
|
+
Modesty.metrics[:foo].count.should == i*2
|
|
21
|
+
end
|
|
22
|
+
end.should_not raise_error
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
after :all do
|
|
26
|
+
Modesty.data.flushdb
|
|
27
|
+
Modesty.set_store :redis, :mock => true
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
require 'modesty'
|
|
2
|
+
|
|
3
|
+
describe "Significance" do
|
|
4
|
+
before :each do
|
|
5
|
+
Modesty.data.flushdb
|
|
6
|
+
Modesty.experiments.clear
|
|
7
|
+
Modesty.metrics.clear
|
|
8
|
+
|
|
9
|
+
@foo = Modesty.new_metric :foo
|
|
10
|
+
@bar = Modesty.new_metric :bar
|
|
11
|
+
|
|
12
|
+
@e = Modesty.new_experiment :baz do |e|
|
|
13
|
+
e.metrics :foo, :bar
|
|
14
|
+
|
|
15
|
+
e.conversion :foo_conv, :on => [:foo, :bar]
|
|
16
|
+
e.distribution :foo_dist, :on => :foo
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
@foo_dist = @e.stats[:foo_dist]
|
|
20
|
+
@foo_conv = @e.stats[:foo_conv]
|
|
21
|
+
|
|
22
|
+
Modesty.identify! 1
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
it "handles significant distribution data" do
|
|
26
|
+
250.times do |uid|
|
|
27
|
+
@e.chooses :experiment, :for => uid
|
|
28
|
+
Modesty.track! :foo, rand(200), :with => {:user => uid}
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
250.times do |uid|
|
|
32
|
+
uid = 251 + uid
|
|
33
|
+
@e.chooses :control, :for => uid
|
|
34
|
+
Modesty.track! :foo, rand(100), :with => {:user => uid}
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
@foo_dist.should be_a Modesty::Experiment::DistributionStat
|
|
38
|
+
an = @foo_dist.analysis
|
|
39
|
+
an.should be_a Hash
|
|
40
|
+
an[:control][:mean].should be_close(50, 10)
|
|
41
|
+
an[:experiment][:mean].should be_close(100, 20)
|
|
42
|
+
an[:control][:size].should == 250
|
|
43
|
+
an[:experiment][:size].should == 250
|
|
44
|
+
|
|
45
|
+
sig = @foo_dist.significance
|
|
46
|
+
sig.should be_a Float
|
|
47
|
+
sig.should be < 0.01
|
|
48
|
+
@foo_dist.should be_significant
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
it "handles insignificant distribution data" do
|
|
52
|
+
@e.chooses :experiment
|
|
53
|
+
250.times do
|
|
54
|
+
Modesty.track! :foo, 1+rand(10)
|
|
55
|
+
end
|
|
56
|
+
@e.chooses :control
|
|
57
|
+
250.times do
|
|
58
|
+
Modesty.track! :foo, 1+rand(10)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
sig = @foo_dist.significance
|
|
62
|
+
sig.should be_nil
|
|
63
|
+
@foo_dist.should_not be_significant
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
it "handles significant conversion data" do
|
|
67
|
+
@e.chooses :experiment
|
|
68
|
+
500.times do
|
|
69
|
+
Modesty.track! :foo, 1+rand(5)
|
|
70
|
+
Modesty.track! :bar, 100
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
@e.chooses :control
|
|
74
|
+
500.times do
|
|
75
|
+
Modesty.track! :foo, 1+rand(100)
|
|
76
|
+
Modesty.track! :bar, 100
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
@foo_conv.should be_a Modesty::Experiment::ConversionStat
|
|
80
|
+
sig = @foo_conv.significance
|
|
81
|
+
sig.should_not be_nil
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
describe "Statistics with blocks" do
|
|
86
|
+
before :each do
|
|
87
|
+
Modesty.data.flushdb
|
|
88
|
+
Modesty.metrics.clear
|
|
89
|
+
Modesty.experiments.clear
|
|
90
|
+
|
|
91
|
+
Modesty.new_metric :foo
|
|
92
|
+
Modesty.new_metric :bar
|
|
93
|
+
|
|
94
|
+
@e = Modesty.new_experiment :baz do |e|
|
|
95
|
+
e.metrics :foo, :bar
|
|
96
|
+
|
|
97
|
+
e.distribution :special_dist do |metrics|
|
|
98
|
+
metrics[:foo].distribution + metrics[:bar].distribution
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
e.conversion :special_conv do |metrics|
|
|
102
|
+
[
|
|
103
|
+
metrics[:foo].unique(:users),
|
|
104
|
+
metrics[:foo].count
|
|
105
|
+
]
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
(1..500).each do |i|
|
|
110
|
+
Modesty.identify!(i)
|
|
111
|
+
Modesty.group :baz
|
|
112
|
+
Modesty.track! :foo, 1+rand(i)
|
|
113
|
+
Modesty.track! :bar, 1+rand(501-i)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
it "uses the blocks for distribution" do
|
|
119
|
+
three_days = @e.stats[:special_dist].data(3.days.ago..Date.today)
|
|
120
|
+
three_days.should == {
|
|
121
|
+
:control => (
|
|
122
|
+
@e.metrics(:control)[:foo].distribution(3.days.ago..Date.today).sum +
|
|
123
|
+
@e.metrics(:control)[:bar].distribution(3.days.ago, Date.today).sum
|
|
124
|
+
),
|
|
125
|
+
:experiment => (
|
|
126
|
+
@e.metrics(:experiment)[:foo].distribution(3.days.ago, :today).sum +
|
|
127
|
+
@e.metrics(:experiment)[:bar].distribution(3.days.ago..Date.today).sum
|
|
128
|
+
)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
three_days.should == @e.stats[:special_dist].data(3.days.ago, :today)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
it "uses the blocks for conversion" do
|
|
135
|
+
three_days = @e.stats[:special_conv].data(3.days.ago..Date.today)
|
|
136
|
+
three_days.sort.should == [
|
|
137
|
+
[
|
|
138
|
+
@e.metrics(:experiment)[:foo].unique(:users, 3.days.ago, :today).sum,
|
|
139
|
+
@e.metrics(:experiment)[:foo].count(3.days.ago, Date.today).sum
|
|
140
|
+
],
|
|
141
|
+
[
|
|
142
|
+
@e.metrics(:control)[:foo].unique(:users, 3.days.ago..Date.today).sum,
|
|
143
|
+
@e.metrics(:control)[:foo].count(3.days.ago, :today).sum
|
|
144
|
+
],
|
|
145
|
+
].sort
|
|
146
|
+
end
|
|
147
|
+
end
|
data/spec/spec.opts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
--color
|
data/vendor/.piston.yml
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
Usage:
|
|
2
|
+
|
|
3
|
+
r = MockRedis.new(:some, :cool => :options)
|
|
4
|
+
|
|
5
|
+
now pretend r is a redis client. see github.com/ezmobius/redis-rb.
|
|
6
|
+
DO NOT USE IN PRODUCTION. PLZ. KTHX
|
|
7
|
+
|
|
8
|
+
This was based on a random file in assaf's "vanity", but as I extended it, it seemed like it needed its own repo.
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
class MockRedis
|
|
2
|
+
module HashMethods
|
|
3
|
+
def hset(key, hkey, val)
|
|
4
|
+
case h = self.hash[key]
|
|
5
|
+
when nil then self.hash[key] = {hkey => val.to_s}
|
|
6
|
+
when Hash then h[hkey] = val.to_s
|
|
7
|
+
else fail "Not a hash"
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def hget(key, hkey)
|
|
12
|
+
case h = self.hash[key]
|
|
13
|
+
when nil then nil
|
|
14
|
+
when Hash then h[hkey]
|
|
15
|
+
else fail "Not a hash"
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def hincrby(key, hkey, val)
|
|
20
|
+
case h = self.hash[key]
|
|
21
|
+
when nil then self.hash[key] = {hkey => val.to_s}
|
|
22
|
+
when Hash then h[hkey] = (h[hkey].to_i + val).to_s
|
|
23
|
+
else fail "Not a hash"
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def hgetall(key)
|
|
28
|
+
case h = self.hash[key]
|
|
29
|
+
when nil then {}
|
|
30
|
+
when Hash then h
|
|
31
|
+
else fail "Not a hash"
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def hkeys(key)
|
|
36
|
+
case h = self.hash[key]
|
|
37
|
+
when nil then []
|
|
38
|
+
when Hash then h.keys
|
|
39
|
+
else fail "Not a hash"
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def hvals(key)
|
|
44
|
+
case h = self.hash[key]
|
|
45
|
+
when nil then []
|
|
46
|
+
when Hash then h.values
|
|
47
|
+
else fail "Not a hash"
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def hlen(key)
|
|
52
|
+
case h = self.hash[key]
|
|
53
|
+
when nil then 0
|
|
54
|
+
when Hash then h.keys.count
|
|
55
|
+
else fail "Not a hash"
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
include HashMethods
|
|
61
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
class MockRedis
|
|
2
|
+
module MiscMethods
|
|
3
|
+
def hash
|
|
4
|
+
@@hash ||= {}
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def del(*keys)
|
|
8
|
+
keys.flatten.each do |key|
|
|
9
|
+
self.hash.delete key
|
|
10
|
+
end
|
|
11
|
+
"OK"
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def exists(key)
|
|
15
|
+
self.hash.has_key?(key)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def type(key)
|
|
19
|
+
case thing = self.hash[key]
|
|
20
|
+
when nil then "none"
|
|
21
|
+
when String then "string"
|
|
22
|
+
when Array then "list"
|
|
23
|
+
when Set then "set"
|
|
24
|
+
when Hash then "hash"
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def rename(old, new)
|
|
29
|
+
self.hash[new] = self.hash[old]
|
|
30
|
+
self.hash.delete(old)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def renamenx(old, new)
|
|
34
|
+
rename(old, new) unless exists(new)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def keys(pattern)
|
|
38
|
+
regexp = Regexp.new(pattern.split("*").map { |r| Regexp.escape(r) }.join(".*"))
|
|
39
|
+
self.hash.keys.select { |key| key =~ regexp }
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def randomkey
|
|
43
|
+
self.hash.keys[rand(dbsize)]
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def dbsize
|
|
47
|
+
self.hash.keys.count
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def flushdb
|
|
51
|
+
self.hash.clear
|
|
52
|
+
end
|
|
53
|
+
alias flushall flushdb
|
|
54
|
+
|
|
55
|
+
def multi
|
|
56
|
+
yield if block_given?
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def ping
|
|
60
|
+
"PONG"
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def pipelined
|
|
64
|
+
yield
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
include MiscMethods
|
|
69
|
+
end
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
class MockRedis
|
|
2
|
+
require 'set'
|
|
3
|
+
|
|
4
|
+
module SetMethods
|
|
5
|
+
def sadd(key, value)
|
|
6
|
+
fail_unless_set(key)
|
|
7
|
+
value = value.to_s
|
|
8
|
+
case set = self.hash[key]
|
|
9
|
+
when nil then self.hash[key] = Set.new([value])
|
|
10
|
+
when Set then set.add value
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def srem(key, value)
|
|
15
|
+
value = value.to_s
|
|
16
|
+
fail_unless_set(key)
|
|
17
|
+
case set = self.hash[key]
|
|
18
|
+
when nil then return
|
|
19
|
+
when Set then set.delete(value)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def sismember(key, value)
|
|
24
|
+
fail_unless_set(key)
|
|
25
|
+
case set = self.hash[key]
|
|
26
|
+
when nil then return false ; puts "no set here"
|
|
27
|
+
when Set then set.include?(value.to_s)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def spop(key, val)
|
|
32
|
+
fail_unless_set(key)
|
|
33
|
+
case set = self.hash[key]
|
|
34
|
+
when nil then nil
|
|
35
|
+
when Set
|
|
36
|
+
el = set.to_a[rand(set.size)]
|
|
37
|
+
set.delete el
|
|
38
|
+
el
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def smove(src_key, dst_key, member)
|
|
43
|
+
fail_unless_set(dst_key)
|
|
44
|
+
if el = self.srem(src_key, member)
|
|
45
|
+
self.sadd(dst_key, member)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def srandmember(key)
|
|
50
|
+
fail_unless_set(key)
|
|
51
|
+
case set = self.hash[key]
|
|
52
|
+
when nil then nil
|
|
53
|
+
when Set then set.to_a[rand(set.size)]
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def smembers(key)
|
|
58
|
+
fail_unless_set(key)
|
|
59
|
+
case set = self.hash[key]
|
|
60
|
+
when nil then []
|
|
61
|
+
when Set then set.to_a
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def scard(key)
|
|
66
|
+
fail_unless_set(key)
|
|
67
|
+
case set = self.hash[key]
|
|
68
|
+
when nil then 0
|
|
69
|
+
when Set then set.size
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def sinter(*keys)
|
|
74
|
+
keys.each { |k| fail_unless_set(k) }
|
|
75
|
+
return Set.new if keys.any? { |k| self.hash[k].nil? }
|
|
76
|
+
keys.inject do |set, key|
|
|
77
|
+
set & self.hash[key]
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def sunion(*keys)
|
|
82
|
+
keys.each { |k| fail_unless_set(k) }
|
|
83
|
+
keys.inject(Set.new) do |set, key|
|
|
84
|
+
return set if self.hash[key].nil?
|
|
85
|
+
set | self.hash[key]
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def sdiff(first, *others)
|
|
90
|
+
[first, *others].each { |k| fail_unless_set(k) }
|
|
91
|
+
others = others.map { |k| self.hash[k] || Set.new }
|
|
92
|
+
others.inject(first) do |memo, set|
|
|
93
|
+
memo - self.hash[set]
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
private
|
|
98
|
+
def is_a_set?(key)
|
|
99
|
+
self.hash[key].is_a?(Set) || self.hash[key].nil?
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def fail_unless_set(key)
|
|
103
|
+
fail "Not a set" unless is_a_set?(key)
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
include SetMethods
|
|
108
|
+
end
|