multi_redis 0.2.0 → 0.3.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.
- checksums.yaml +4 -4
- data/Gemfile +7 -10
- data/README.md +37 -45
- data/VERSION +1 -1
- data/lib/multi_redis.rb +19 -159
- data/lib/multi_redis/context.rb +24 -8
- data/lib/multi_redis/data.rb +7 -20
- data/lib/multi_redis/executor.rb +97 -0
- data/lib/multi_redis/extension.rb +13 -0
- data/lib/multi_redis/future.rb +23 -0
- data/lib/multi_redis/operation.rb +61 -0
- data/lib/multi_redis/step.rb +17 -0
- metadata +35 -44
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 679e1a349b9f03a3c6c16634f9a5e8b2051ae5ca
|
4
|
+
data.tar.gz: 940d7c657fa3dd83977a536e78e44d366c910072
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5b47def10a775566a8c70cd4b3bbddef629349ebdfe197c2daf6170df572f872a9d7b090fd24d6e530eaf9ffe0067d8314a915aff6d9a8d0ad9eb77374c96e16
|
7
|
+
data.tar.gz: ceeca19ecbd97dd7a51ab6dd9d5f4d5b30391bdc8980e0c7eced2f7f45c3c1ebc7e074b74664bf5bb1066eb79378e20c96c6e5aaab1d275853f27beb21d0ae29
|
data/Gemfile
CHANGED
@@ -2,15 +2,12 @@ source "https://rubygems.org"
|
|
2
2
|
|
3
3
|
gem 'redis', '~> 3.0'
|
4
4
|
|
5
|
-
# Add dependencies to develop your gem here.
|
6
|
-
# Include everything needed to run rake, tests, features, etc.
|
7
5
|
group :development do
|
8
|
-
gem 'bundler'
|
9
|
-
gem 'rake'
|
10
|
-
gem 'rspec'
|
11
|
-
gem 'jeweler'
|
12
|
-
gem '
|
13
|
-
gem '
|
14
|
-
gem '
|
15
|
-
gem 'coveralls', require: false
|
6
|
+
gem 'bundler', '~> 1.5'
|
7
|
+
gem 'rake', '~> 10.1'
|
8
|
+
gem 'rspec', '~> 2.14'
|
9
|
+
gem 'jeweler', '~> 2.0'
|
10
|
+
gem 'rake-version', '~> 0.4'
|
11
|
+
gem 'simplecov', '~> 0.8'
|
12
|
+
gem 'coveralls', '~> 0.7', require: false
|
16
13
|
end
|
data/README.md
CHANGED
@@ -25,48 +25,46 @@ Assume you have two separate methods that call redis:
|
|
25
25
|
$redis = Redis.new
|
26
26
|
$redis.set 'key1', 'foo'
|
27
27
|
$redis.set 'key2', 'bar'
|
28
|
-
$redis.set 'key3', 'baz'
|
29
28
|
|
30
29
|
class MyRedisClass
|
31
30
|
|
32
31
|
def do_stuff
|
33
|
-
|
34
|
-
# run two calls atomically in a MULTI/EXEC
|
35
|
-
values = $redis.multi do
|
36
|
-
$redis.get 'key1'
|
37
|
-
$redis.getset 'key2', 'newvalue'
|
38
|
-
end
|
39
|
-
|
40
|
-
"value 1 is #{values[0]}, value 2 is #{values[1]}"
|
32
|
+
$redis.get 'key1'
|
41
33
|
end
|
42
34
|
|
43
35
|
def do_other_stuff
|
44
|
-
|
45
|
-
"hey #{value}"
|
36
|
+
$redis.get 'key2'
|
46
37
|
end
|
47
38
|
end
|
48
39
|
|
49
40
|
o = MyRedisClass.new
|
50
|
-
o.do_stuff #=> "
|
51
|
-
o.do_other_stuff #=> "
|
41
|
+
o.do_stuff #=> "foo"
|
42
|
+
o.do_other_stuff #=> "bar"
|
52
43
|
```
|
53
44
|
|
54
|
-
This works, but the redis client executes two separate requests to the server:
|
45
|
+
This works, but the redis client executes two separate requests to the server, and waits for the result of the first one to start the second one:
|
55
46
|
|
56
47
|
```
|
57
48
|
Request 1:
|
58
|
-
-
|
59
|
-
- GET foo
|
60
|
-
- GETSET bar newvalue
|
61
|
-
- EXEC
|
49
|
+
- GET key1
|
62
50
|
|
63
51
|
Request 2:
|
64
|
-
- GET
|
52
|
+
- GET key2
|
53
|
+
```
|
54
|
+
|
55
|
+
`redis-rb` allows you to run both in the same command pipeline:
|
56
|
+
|
57
|
+
```rb
|
58
|
+
results = $redis.pipelined do
|
59
|
+
$redis.get 'key1'
|
60
|
+
$redis.get 'key2'
|
61
|
+
end
|
62
|
+
|
63
|
+
results[0] #=> "foo"
|
64
|
+
results[1] #=> "bar"
|
65
65
|
```
|
66
66
|
|
67
|
-
|
68
|
-
One round trip could be saved by executing the second request in the same MULTI/EXEC block.
|
69
|
-
But it would be hard to refactor these two methods to do that while still keeping them separate.
|
67
|
+
But it would be hard to refactor the two methods to use a pipeline while still keeping them separate.
|
70
68
|
|
71
69
|
Multi Redis provides a pattern to structure this code so that your separate redis calls may be executed together in one request when needed.
|
72
70
|
|
@@ -74,42 +72,39 @@ Multi Redis provides a pattern to structure this code so that your separate redi
|
|
74
72
|
$redis = Redis.new
|
75
73
|
$redis.set 'key1', 'foo'
|
76
74
|
$redis.set 'key2', 'bar'
|
77
|
-
$redis.set 'key3', 'baz'
|
78
75
|
|
79
|
-
# Create a redis operation, i.e. an operation that performs redis calls.
|
76
|
+
# Create a redis operation, i.e. an operation that performs redis calls, for the first method.
|
80
77
|
do_stuff = MultiRedis::Operation.new do
|
81
78
|
|
82
|
-
#
|
79
|
+
# Pipelined blocks will be run in a command pipeline.
|
83
80
|
# All redis commands will return futures inside this block, so you can't use the values immediately.
|
84
|
-
|
85
|
-
|
86
|
-
mr.data.value1 = $redis.get 'key1'
|
87
|
-
mr.data.value2 = $redis.getset 'key2', 'newvalue'
|
81
|
+
pipelined do |mr|
|
82
|
+
$redis.get 'key1'
|
88
83
|
end
|
89
84
|
|
90
|
-
#
|
91
|
-
# The
|
85
|
+
# This run block will be executed after the pipelined block is completed and all futures have been resolved.
|
86
|
+
# The #last_replies method of the Multi Redis context will return the results of all redis calls in the pipelined block.
|
92
87
|
run do |mr|
|
93
|
-
|
88
|
+
mr.last_replies[0] # => "foo"
|
94
89
|
end
|
95
90
|
end
|
96
91
|
|
97
|
-
# The return value of the operation is that of the last
|
98
|
-
result = do_stuff.execute #=> "
|
92
|
+
# The return value of the operation is that of the last block.
|
93
|
+
result = do_stuff.execute #=> "foo"
|
99
94
|
|
100
|
-
# Create the
|
95
|
+
# Create the redis operation for the other method.
|
101
96
|
do_other_stuff = MultiRedis::Operation.new do
|
102
97
|
|
103
98
|
multi do |mr|
|
104
|
-
|
99
|
+
$redis.get 'key2'
|
105
100
|
end
|
106
101
|
|
107
102
|
run do |mr|
|
108
|
-
|
103
|
+
mr.last_replies[0] #=> "bar"
|
109
104
|
end
|
110
105
|
end
|
111
106
|
|
112
|
-
result = do_other_stuff.execute #=> "
|
107
|
+
result = do_other_stuff.execute #=> "bar"
|
113
108
|
```
|
114
109
|
|
115
110
|
The two operations can still be executed separately like before, but they can also be combined through Multi Redis:
|
@@ -118,21 +113,18 @@ The two operations can still be executed separately like before, but they can al
|
|
118
113
|
MultiRedis.execute do_stuff, do_other_stuff
|
119
114
|
```
|
120
115
|
|
121
|
-
|
116
|
+
Both redis calls get grouped into the same command pipeline:
|
122
117
|
|
123
118
|
```
|
124
119
|
One request:
|
125
|
-
- MULTI
|
126
120
|
- GET foo
|
127
|
-
-
|
128
|
-
- GET baz
|
129
|
-
- EXEC
|
121
|
+
- GET bar
|
130
122
|
```
|
131
123
|
|
132
|
-
The array of results is
|
124
|
+
The array of results is returned by the `execute` call:
|
133
125
|
|
134
126
|
```rb
|
135
|
-
MultiRedis.execute do_stuff, do_other_stuff #=> [
|
127
|
+
MultiRedis.execute do_stuff, do_other_stuff #=> [ "foo", "bar" ]
|
136
128
|
```
|
137
129
|
|
138
130
|
## Meta
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.3.0
|
data/lib/multi_redis.rb
CHANGED
@@ -1,179 +1,39 @@
|
|
1
|
-
require 'ostruct'
|
2
1
|
require 'redis'
|
3
|
-
require 'thread'
|
4
2
|
|
5
3
|
module MultiRedis
|
6
|
-
VERSION = '0.
|
4
|
+
VERSION = '0.3.0'
|
7
5
|
|
8
|
-
|
9
|
-
|
10
|
-
@executing = false
|
11
|
-
@operations = []
|
12
|
-
@arguments = []
|
13
|
-
|
14
|
-
def self.redis= redis
|
15
|
-
@redis = redis
|
16
|
-
end
|
17
|
-
|
18
|
-
def self.redis
|
19
|
-
@redis
|
6
|
+
class << self
|
7
|
+
attr_accessor :redis
|
20
8
|
end
|
21
9
|
|
22
10
|
def self.execute *args, &block
|
23
11
|
|
24
|
-
|
25
|
-
@operations = args.dup
|
26
|
-
@arguments = []
|
27
|
-
@executing = true
|
28
|
-
yield if block_given?
|
29
|
-
@executing = false
|
30
|
-
[ @operations.dup.tap{ |ops| @operations.clear }, @arguments ]
|
31
|
-
end
|
32
|
-
|
33
|
-
Executor.new(operations, args: arguments).execute
|
34
|
-
end
|
35
|
-
|
36
|
-
def self.executing?
|
37
|
-
@executing
|
38
|
-
end
|
12
|
+
options = args.last.kind_of?(Hash) ? args.pop : {}
|
39
13
|
|
40
|
-
|
41
|
-
|
42
|
-
@
|
43
|
-
@
|
14
|
+
executor = nil
|
15
|
+
@mutex.synchronize do
|
16
|
+
@executor = Executor.new options
|
17
|
+
args.each{ |op| @executor.add op }
|
18
|
+
yield if block_given?
|
19
|
+
executor = @executor
|
20
|
+
@executor = nil
|
44
21
|
end
|
45
|
-
end
|
46
|
-
|
47
|
-
module Extension
|
48
22
|
|
49
|
-
|
50
|
-
op = Operation.new self, options, &block
|
51
|
-
define_method symbol do |*args|
|
52
|
-
op.execute *args
|
53
|
-
end
|
54
|
-
self
|
55
|
-
end
|
23
|
+
executor.execute
|
56
24
|
end
|
57
25
|
|
58
|
-
|
59
|
-
|
60
|
-
def initialize operations, options = {}
|
61
|
-
@operations = operations
|
62
|
-
@arguments = options[:args] || []
|
63
|
-
@redis = options[:redis]
|
64
|
-
end
|
65
|
-
|
66
|
-
def execute options = {}
|
67
|
-
|
68
|
-
redis = @redis || MultiRedis.redis
|
69
|
-
contexts = Array.new(@operations.length){ |i| Context.new redis }
|
70
|
-
stacks = @operations.collect{ |op| op.steps.dup }
|
71
|
-
args = stacks.collect.with_index{ |a,i| @arguments[i] || [] }
|
72
|
-
final_results = Array.new @operations.length
|
26
|
+
private
|
73
27
|
|
74
|
-
|
75
|
-
|
76
|
-
# execute all non-multi steps
|
77
|
-
stacks.each_with_index do |steps,i|
|
78
|
-
final_results[i] = steps.shift.execute(contexts[i], args[i]) while steps.first && !steps.first.multi_type
|
79
|
-
end
|
80
|
-
|
81
|
-
# execute all pipelined steps, if any
|
82
|
-
pipelined_steps = stacks.collect{ |steps| steps.first && steps.first.multi_type == :pipelined ? steps.shift : nil }
|
83
|
-
if pipelined_steps.any?
|
84
|
-
results = []
|
85
|
-
redis.pipelined do
|
86
|
-
pipelined_steps.each_with_index do |step,i|
|
87
|
-
if step
|
88
|
-
final_results[i] = step.execute(contexts[i], args[i])
|
89
|
-
contexts[i].last_results = redis.client.futures[results.length, redis.client.futures.length]
|
90
|
-
results += contexts[i].last_results
|
91
|
-
end
|
92
|
-
end
|
93
|
-
end
|
94
|
-
pipelined_steps.each_with_index{ |step,i| contexts[i].resolve_futures! if step }
|
95
|
-
end
|
96
|
-
|
97
|
-
# execute all multi steps, if any
|
98
|
-
multi_steps = stacks.collect{ |steps| steps.first && steps.first.multi_type == :multi ? steps.shift : nil }
|
99
|
-
if multi_steps.any?
|
100
|
-
results = []
|
101
|
-
redis.multi do
|
102
|
-
multi_steps.each_with_index do |step,i|
|
103
|
-
if step
|
104
|
-
final_results[i] = step.execute(contexts[i], args[i])
|
105
|
-
contexts[i].last_results = redis.client.futures[results.length, redis.client.futures.length]
|
106
|
-
results += contexts[i].last_results
|
107
|
-
end
|
108
|
-
end
|
109
|
-
end
|
110
|
-
multi_steps.each_with_index{ |step,i| contexts[i].resolve_futures! if step }
|
111
|
-
end
|
112
|
-
end
|
113
|
-
|
114
|
-
final_results
|
115
|
-
end
|
116
|
-
end
|
117
|
-
|
118
|
-
class Operation
|
119
|
-
attr_reader :steps
|
120
|
-
|
121
|
-
def initialize *args, &block
|
122
|
-
|
123
|
-
options = args.last.kind_of?(Hash) ? args.pop : {}
|
124
|
-
|
125
|
-
@target = args.shift || options[:target] || self
|
126
|
-
@redis = options[:redis]
|
127
|
-
@steps = []
|
128
|
-
|
129
|
-
DSL.new(self).instance_eval &block
|
130
|
-
end
|
131
|
-
|
132
|
-
def execute *args
|
133
|
-
if MultiRedis.executing?
|
134
|
-
MultiRedis.register_operation self, *args
|
135
|
-
else
|
136
|
-
Executor.new([ self ], args: [ args ], redis: @redis).execute.first
|
137
|
-
end
|
138
|
-
end
|
139
|
-
|
140
|
-
def add_step multi_type = nil, &block
|
141
|
-
@steps << Step.new(@target, multi_type, block)
|
142
|
-
end
|
143
|
-
|
144
|
-
class DSL
|
145
|
-
|
146
|
-
def initialize op
|
147
|
-
@op = op
|
148
|
-
end
|
149
|
-
|
150
|
-
def multi &block
|
151
|
-
@op.add_step :multi, &block
|
152
|
-
end
|
153
|
-
|
154
|
-
def pipelined &block
|
155
|
-
@op.add_step :pipelined, &block
|
156
|
-
end
|
28
|
+
@mutex = Mutex.new
|
29
|
+
@executor = nil
|
157
30
|
|
158
|
-
|
159
|
-
|
160
|
-
end
|
161
|
-
end
|
31
|
+
def self.executor
|
32
|
+
@executor
|
162
33
|
end
|
163
34
|
|
164
|
-
|
165
|
-
|
166
|
-
def initialize target, multi_type, block
|
167
|
-
@target, @multi_type, @block = target, multi_type, block
|
168
|
-
end
|
169
|
-
|
170
|
-
def execute context, *args
|
171
|
-
@target.instance_exec *args.unshift(context), &@block
|
172
|
-
end
|
173
|
-
|
174
|
-
def multi_type
|
175
|
-
@multi_type
|
176
|
-
end
|
35
|
+
def self.executing?
|
36
|
+
!!@executor
|
177
37
|
end
|
178
38
|
end
|
179
39
|
|
data/lib/multi_redis/context.rb
CHANGED
@@ -1,13 +1,28 @@
|
|
1
|
-
|
2
1
|
module MultiRedis
|
3
2
|
|
4
3
|
class Context
|
5
|
-
attr_accessor :
|
4
|
+
attr_accessor :last_result
|
5
|
+
attr_accessor :last_replies
|
6
6
|
|
7
|
-
def initialize redis
|
8
|
-
@last_results = []
|
9
|
-
@data = Data.new
|
7
|
+
def initialize redis, shared_context = nil
|
10
8
|
@redis = redis
|
9
|
+
@data = Data.new
|
10
|
+
@last_replies = []
|
11
|
+
@shared_context = shared_context
|
12
|
+
end
|
13
|
+
|
14
|
+
def execute operation, *args
|
15
|
+
@last_result = operation.execute self, *args
|
16
|
+
if @resolve = @redis.client.respond_to?(:futures)
|
17
|
+
@last_replies = @redis.client.futures[@shared_context.last_replies.length, @redis.client.futures.length]
|
18
|
+
@shared_context.last_replies.concat @last_replies
|
19
|
+
end
|
20
|
+
@shared_context.last_result = @last_result
|
21
|
+
@last_result
|
22
|
+
end
|
23
|
+
|
24
|
+
def shared
|
25
|
+
@shared_context
|
11
26
|
end
|
12
27
|
|
13
28
|
def redis
|
@@ -19,10 +34,11 @@ module MultiRedis
|
|
19
34
|
end
|
20
35
|
|
21
36
|
def resolve_futures!
|
22
|
-
|
23
|
-
|
37
|
+
return unless @resolve
|
38
|
+
@data.each_key do |k|
|
39
|
+
@data[k] = @data[k].value if @data[k].is_a? Redis::Future
|
24
40
|
end
|
25
|
-
@
|
41
|
+
@last_replies.collect!{ |r| r.is_a?(Redis::Future) ? r.value : r }
|
26
42
|
end
|
27
43
|
end
|
28
44
|
end
|
data/lib/multi_redis/data.rb
CHANGED
@@ -1,27 +1,14 @@
|
|
1
|
-
|
2
1
|
module MultiRedis
|
3
2
|
|
4
|
-
class Data
|
5
|
-
attr_reader :contents
|
6
|
-
|
7
|
-
def initialize
|
8
|
-
@contents = Hash.new
|
9
|
-
end
|
10
|
-
|
11
|
-
def [] k
|
12
|
-
@contents[k]
|
13
|
-
end
|
14
|
-
|
15
|
-
def []= k, v
|
16
|
-
@contents[k.to_sym] = v
|
17
|
-
end
|
3
|
+
class Data < Hash
|
18
4
|
|
19
5
|
def method_missing symbol, *args, &block
|
20
|
-
if
|
21
|
-
|
22
|
-
elsif m = symbol.to_s.match(/\A(.*)\=\Z/)
|
23
|
-
|
24
|
-
|
6
|
+
if args.empty?
|
7
|
+
self[symbol]
|
8
|
+
elsif args.length == 1 && m = symbol.to_s.match(/\A(.*)\=\Z/)
|
9
|
+
acc = m[1].to_sym
|
10
|
+
raise ArgumentError, "Cannot set property #{acc}, method ##{acc} already exists" if respond_to? acc
|
11
|
+
self[acc] = args[0]
|
25
12
|
else
|
26
13
|
super symbol, *args, &block
|
27
14
|
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
module MultiRedis
|
2
|
+
|
3
|
+
class Executor
|
4
|
+
|
5
|
+
def initialize options = {}
|
6
|
+
@operations = []
|
7
|
+
@redis = options[:redis]
|
8
|
+
end
|
9
|
+
|
10
|
+
def add operation, *args
|
11
|
+
@operations << { op: operation, args: args }
|
12
|
+
end
|
13
|
+
|
14
|
+
def execute options = {}
|
15
|
+
|
16
|
+
redis = @redis || MultiRedis.redis
|
17
|
+
shared_context = Context.new redis
|
18
|
+
|
19
|
+
total = 0
|
20
|
+
execution = @operations.collect do |operation|
|
21
|
+
total += operation[:op].steps.length
|
22
|
+
OperationExecution.new operation[:op], operation[:args], shared_context
|
23
|
+
end
|
24
|
+
|
25
|
+
while execution.any?{ |oe| !oe.done? } && total >= 1
|
26
|
+
total -= 1 # safeguard against infinite loop
|
27
|
+
|
28
|
+
TYPES.each do |type|
|
29
|
+
|
30
|
+
execution.each do |oe|
|
31
|
+
oe.execute_current_step while oe.next? :call
|
32
|
+
end
|
33
|
+
|
34
|
+
if execution.any?{ |oe| oe.next? type }
|
35
|
+
shared_context.last_replies.clear
|
36
|
+
redis.send type do
|
37
|
+
execution.each do |oe|
|
38
|
+
oe.execute_current_step if oe.next? type
|
39
|
+
end
|
40
|
+
end
|
41
|
+
execution.each{ |oe| oe.resolve_futures! }
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
execution.each{ |oe| oe.resolve_operation_future! }
|
47
|
+
execution.collect!{ |oe| oe.final_results }
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
TYPES = [ :pipelined, :multi ]
|
53
|
+
|
54
|
+
class OperationExecution
|
55
|
+
attr_reader :final_results
|
56
|
+
|
57
|
+
def initialize operation, args, shared_context
|
58
|
+
|
59
|
+
@operation = operation
|
60
|
+
@args = args
|
61
|
+
|
62
|
+
@context = Context.new shared_context.redis, shared_context
|
63
|
+
@steps = operation.steps
|
64
|
+
|
65
|
+
@current_index = 0
|
66
|
+
end
|
67
|
+
|
68
|
+
def done?
|
69
|
+
!current_step
|
70
|
+
end
|
71
|
+
|
72
|
+
def next? type
|
73
|
+
current_step && current_step.type == type
|
74
|
+
end
|
75
|
+
|
76
|
+
def execute_current_step
|
77
|
+
results = @context.execute current_step, *@args
|
78
|
+
@current_index += 1
|
79
|
+
@final_results = results
|
80
|
+
end
|
81
|
+
|
82
|
+
def resolve_futures!
|
83
|
+
@context.resolve_futures!
|
84
|
+
end
|
85
|
+
|
86
|
+
def resolve_operation_future!
|
87
|
+
@operation.future.value = @final_results if @operation.future
|
88
|
+
end
|
89
|
+
|
90
|
+
private
|
91
|
+
|
92
|
+
def current_step
|
93
|
+
@steps[@current_index]
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module MultiRedis
|
2
|
+
|
3
|
+
class FutureNotReady < RuntimeError
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
super "Value will be available once the operation executes."
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
class Future
|
11
|
+
FutureNotReady = ::MultiRedis::FutureNotReady.new
|
12
|
+
attr_writer :value
|
13
|
+
|
14
|
+
def initialize value = nil
|
15
|
+
@value = value || FutureNotReady
|
16
|
+
end
|
17
|
+
|
18
|
+
def value
|
19
|
+
raise @value if @value.kind_of? RuntimeError
|
20
|
+
@value
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module MultiRedis
|
2
|
+
|
3
|
+
class Operation
|
4
|
+
attr_accessor :redis
|
5
|
+
attr_reader :steps, :future
|
6
|
+
|
7
|
+
def initialize options = {}, &block
|
8
|
+
|
9
|
+
@target = options[:target] || self
|
10
|
+
@redis = options[:redis]
|
11
|
+
@steps = []
|
12
|
+
|
13
|
+
configure &block if block
|
14
|
+
end
|
15
|
+
|
16
|
+
def configure &block
|
17
|
+
DSL.new(self).instance_eval &block
|
18
|
+
end
|
19
|
+
|
20
|
+
def execute *args
|
21
|
+
if MultiRedis.executing?
|
22
|
+
MultiRedis.executor.add self, *args
|
23
|
+
@future = Future.new
|
24
|
+
else
|
25
|
+
e = Executor.new redis: @redis
|
26
|
+
e.add self, *args
|
27
|
+
e.execute.first.tap do |result|
|
28
|
+
@future = Future.new result
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def add type, &block
|
34
|
+
raise ArgumentError, "Unknown type #{type}, must be one of #{TYPES.join ', '}." unless TYPES.include? type
|
35
|
+
@steps << Step.new(@target, type, block)
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
TYPES = [ :call, :pipelined, :multi ]
|
41
|
+
|
42
|
+
class DSL
|
43
|
+
|
44
|
+
def initialize op
|
45
|
+
@op = op
|
46
|
+
end
|
47
|
+
|
48
|
+
def multi &block
|
49
|
+
@op.add :multi, &block
|
50
|
+
end
|
51
|
+
|
52
|
+
def pipelined &block
|
53
|
+
@op.add :pipelined, &block
|
54
|
+
end
|
55
|
+
|
56
|
+
def run &block
|
57
|
+
@op.add :call, &block
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module MultiRedis
|
2
|
+
|
3
|
+
class Step
|
4
|
+
|
5
|
+
def initialize target, type, block
|
6
|
+
@target, @type, @block = target, type, block
|
7
|
+
end
|
8
|
+
|
9
|
+
def execute context, *args
|
10
|
+
@target.instance_exec *args.unshift(context), &@block
|
11
|
+
end
|
12
|
+
|
13
|
+
def type
|
14
|
+
@type
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: multi_redis
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Simon Oulevay
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-01-
|
11
|
+
date: 2014-01-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: redis
|
@@ -28,114 +28,100 @@ dependencies:
|
|
28
28
|
name: bundler
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- - "
|
31
|
+
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
33
|
+
version: '1.5'
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- - "
|
38
|
+
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: '
|
40
|
+
version: '1.5'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: rake
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
|
-
- - "
|
45
|
+
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: '
|
47
|
+
version: '10.1'
|
48
48
|
type: :development
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
|
-
- - "
|
52
|
+
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: '
|
54
|
+
version: '10.1'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: rspec
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
|
-
- - "
|
59
|
+
- - "~>"
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version: '
|
61
|
+
version: '2.14'
|
62
62
|
type: :development
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
|
-
- - "
|
66
|
+
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
|
-
version: '
|
68
|
+
version: '2.14'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: jeweler
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
72
72
|
requirements:
|
73
|
-
- - "
|
74
|
-
- !ruby/object:Gem::Version
|
75
|
-
version: '0'
|
76
|
-
type: :development
|
77
|
-
prerelease: false
|
78
|
-
version_requirements: !ruby/object:Gem::Requirement
|
79
|
-
requirements:
|
80
|
-
- - ">="
|
81
|
-
- !ruby/object:Gem::Version
|
82
|
-
version: '0'
|
83
|
-
- !ruby/object:Gem::Dependency
|
84
|
-
name: gem-release
|
85
|
-
requirement: !ruby/object:Gem::Requirement
|
86
|
-
requirements:
|
87
|
-
- - ">="
|
73
|
+
- - "~>"
|
88
74
|
- !ruby/object:Gem::Version
|
89
|
-
version: '0'
|
75
|
+
version: '2.0'
|
90
76
|
type: :development
|
91
77
|
prerelease: false
|
92
78
|
version_requirements: !ruby/object:Gem::Requirement
|
93
79
|
requirements:
|
94
|
-
- - "
|
80
|
+
- - "~>"
|
95
81
|
- !ruby/object:Gem::Version
|
96
|
-
version: '0'
|
82
|
+
version: '2.0'
|
97
83
|
- !ruby/object:Gem::Dependency
|
98
84
|
name: rake-version
|
99
85
|
requirement: !ruby/object:Gem::Requirement
|
100
86
|
requirements:
|
101
|
-
- - "
|
87
|
+
- - "~>"
|
102
88
|
- !ruby/object:Gem::Version
|
103
|
-
version: '0'
|
89
|
+
version: '0.4'
|
104
90
|
type: :development
|
105
91
|
prerelease: false
|
106
92
|
version_requirements: !ruby/object:Gem::Requirement
|
107
93
|
requirements:
|
108
|
-
- - "
|
94
|
+
- - "~>"
|
109
95
|
- !ruby/object:Gem::Version
|
110
|
-
version: '0'
|
96
|
+
version: '0.4'
|
111
97
|
- !ruby/object:Gem::Dependency
|
112
98
|
name: simplecov
|
113
99
|
requirement: !ruby/object:Gem::Requirement
|
114
100
|
requirements:
|
115
|
-
- - "
|
101
|
+
- - "~>"
|
116
102
|
- !ruby/object:Gem::Version
|
117
|
-
version: '0'
|
103
|
+
version: '0.8'
|
118
104
|
type: :development
|
119
105
|
prerelease: false
|
120
106
|
version_requirements: !ruby/object:Gem::Requirement
|
121
107
|
requirements:
|
122
|
-
- - "
|
108
|
+
- - "~>"
|
123
109
|
- !ruby/object:Gem::Version
|
124
|
-
version: '0'
|
110
|
+
version: '0.8'
|
125
111
|
- !ruby/object:Gem::Dependency
|
126
112
|
name: coveralls
|
127
113
|
requirement: !ruby/object:Gem::Requirement
|
128
114
|
requirements:
|
129
|
-
- - "
|
115
|
+
- - "~>"
|
130
116
|
- !ruby/object:Gem::Version
|
131
|
-
version: '0'
|
117
|
+
version: '0.7'
|
132
118
|
type: :development
|
133
119
|
prerelease: false
|
134
120
|
version_requirements: !ruby/object:Gem::Requirement
|
135
121
|
requirements:
|
136
|
-
- - "
|
122
|
+
- - "~>"
|
137
123
|
- !ruby/object:Gem::Version
|
138
|
-
version: '0'
|
124
|
+
version: '0.7'
|
139
125
|
description: Allows you to organize your redis calls in separate classes but still
|
140
126
|
execute them atomically with pipelined or multi.
|
141
127
|
email: git@alphahydrae.com
|
@@ -152,6 +138,11 @@ files:
|
|
152
138
|
- lib/multi_redis.rb
|
153
139
|
- lib/multi_redis/context.rb
|
154
140
|
- lib/multi_redis/data.rb
|
141
|
+
- lib/multi_redis/executor.rb
|
142
|
+
- lib/multi_redis/extension.rb
|
143
|
+
- lib/multi_redis/future.rb
|
144
|
+
- lib/multi_redis/operation.rb
|
145
|
+
- lib/multi_redis/step.rb
|
155
146
|
homepage: http://github.com/AlphaHydrae/multi_redis
|
156
147
|
licenses:
|
157
148
|
- MIT
|