redis-objects 0.2.1 → 0.2.2
Sign up to get free protection for your applications and to get access to all the features.
- data/ChangeLog +15 -0
- data/README.rdoc +28 -11
- data/lib/redis/counter.rb +4 -7
- data/lib/redis/helpers/core_commands.rb +46 -0
- data/lib/redis/helpers/serialize.rb +25 -0
- data/lib/redis/list.rb +5 -7
- data/lib/redis/objects/counters.rb +37 -20
- data/lib/redis/objects/lists.rb +19 -9
- data/lib/redis/objects/locks.rb +24 -11
- data/lib/redis/objects/sets.rb +20 -9
- data/lib/redis/objects/values.rb +29 -12
- data/lib/redis/objects.rb +2 -2
- data/lib/redis/set.rb +30 -8
- data/lib/redis/value.rb +7 -9
- data/spec/redis_objects_instance_spec.rb +63 -1
- data/spec/redis_objects_model_spec.rb +188 -1
- data/spec/spec_helper.rb +1 -0
- metadata +6 -4
- data/lib/redis/objects/core.rb +0 -0
- data/lib/redis/serialize.rb +0 -23
data/ChangeLog
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
|
2
|
+
*0.2.2 [Final] (14 December 2009)*
|
3
|
+
|
4
|
+
* Added @set.diff(@set2) with "^" and "-" synonyms (oversight). [Nate Wiger]
|
5
|
+
|
6
|
+
* Implemented Redis core commands in all data types, such as rename. [Nate Wiger]
|
7
|
+
|
8
|
+
* Renamed Redis::Serialize to Redis::Helpers::Serialize to keep Redis:: cleaner. [Nate Wiger]
|
9
|
+
|
10
|
+
* More spec coverage. [Nate Wiger]
|
11
|
+
|
12
|
+
*0.2.1 [Final] (27 November 2009)*
|
13
|
+
|
14
|
+
* First worthwhile public release, with good spec coverage and functionality. [Nate Wiger]
|
15
|
+
|
data/README.rdoc
CHANGED
@@ -6,8 +6,8 @@ the point.
|
|
6
6
|
The killer feature of Redis that it allows you to perform atomic operations
|
7
7
|
on _individual_ data structures, like counters, lists, and sets. You can then use
|
8
8
|
these *with* your existing ActiveRecord/DataMapper/etc models, or in classes that have
|
9
|
-
nothing to do with an ORM or even a database. This gem maps
|
10
|
-
to Ruby objects
|
9
|
+
nothing to do with an ORM or even a database. This gem maps {Redis types}[http://code.google.com/p/redis/wiki/CommandReference]
|
10
|
+
to Ruby objects, via a thin layer over Ezra's +redis+ gem.
|
11
11
|
|
12
12
|
This gem originally arose out of a need for high-concurrency atomic operations;
|
13
13
|
for a fun rant on the topic, see
|
@@ -23,13 +23,14 @@ or by using +new+ with the type of data structure you want to create.
|
|
23
23
|
gem tumble
|
24
24
|
gem install redis-objects
|
25
25
|
|
26
|
-
== Example 1:
|
26
|
+
== Example 1: Standalone Usage
|
27
27
|
|
28
28
|
There is a Ruby object that maps to each Redis type.
|
29
29
|
|
30
30
|
=== Initialization
|
31
31
|
|
32
|
-
|
32
|
+
This gem needs a handle to the +redis+ server. For standalone use, you can
|
33
|
+
either set the $redis global variable to your Redis.new handle:
|
33
34
|
|
34
35
|
$redis = Redis.new(:host => 'localhost', :port => 6379)
|
35
36
|
@value = Redis::Value.new('myvalue')
|
@@ -43,6 +44,7 @@ Or you can pass the Redis handle into the new method:
|
|
43
44
|
|
44
45
|
Create a new counter. The +counter_name+ is the key stored in Redis.
|
45
46
|
|
47
|
+
require 'redis/counter'
|
46
48
|
@counter = Redis::Counter.new('counter_name')
|
47
49
|
@counter.increment
|
48
50
|
@counter.decrement
|
@@ -60,6 +62,7 @@ See the section on "Atomicity" for cool uses of atomic counter blocks.
|
|
60
62
|
|
61
63
|
Lists work just like Ruby arrays:
|
62
64
|
|
65
|
+
require 'redis/list'
|
63
66
|
@list = Redis::List.new('list_name')
|
64
67
|
@list << 'a'
|
65
68
|
@list << 'b'
|
@@ -87,6 +90,7 @@ Complex data types are no problem:
|
|
87
90
|
|
88
91
|
Sets work like the Ruby {Set}[http://ruby-doc.org/core/classes/Set.html] class:
|
89
92
|
|
93
|
+
require 'redis/set'
|
90
94
|
@set = Redis::Set.new('set_name')
|
91
95
|
@set << 'a'
|
92
96
|
@set << 'b'
|
@@ -99,7 +103,7 @@ Sets work like the Ruby {Set}[http://ruby-doc.org/core/classes/Set.html] class:
|
|
99
103
|
@set.clear
|
100
104
|
# etc
|
101
105
|
|
102
|
-
You can perform Redis intersections/unions easily:
|
106
|
+
You can perform Redis intersections/unions/diffs easily:
|
103
107
|
|
104
108
|
@set1 = Redis::Set.new('set1')
|
105
109
|
@set2 = Redis::Set.new('set2')
|
@@ -107,8 +111,11 @@ You can perform Redis intersections/unions easily:
|
|
107
111
|
members = @set1 & @set2 # intersection
|
108
112
|
members = @set1 | @set2 # union
|
109
113
|
members = @set1 + @set2 # union
|
114
|
+
members = @set1 ^ @set2 # union
|
115
|
+
members = @set1 - @set2 # union
|
110
116
|
members = @set1.intersection(@set2, @set3) # multiple
|
111
117
|
members = @set1.union(@set2, @set3) # multiple
|
118
|
+
members = @set1.difference(@set2, @set3) # multiple
|
112
119
|
|
113
120
|
Or store them in Redis:
|
114
121
|
|
@@ -116,6 +123,8 @@ Or store them in Redis:
|
|
116
123
|
members = @set1.redis.get('intername')
|
117
124
|
@set1.unionstore('unionname', @set2, @set3)
|
118
125
|
members = @set1.redis.get('unionname')
|
126
|
+
@set1.diffstore('diffname', @set2, @set3)
|
127
|
+
members = @set1.redis.get('diffname')
|
119
128
|
|
120
129
|
And use complex data types too:
|
121
130
|
|
@@ -125,12 +134,14 @@ And use complex data types too:
|
|
125
134
|
@set2 << {:name => "Jeff", :city => "Del Mar"}
|
126
135
|
|
127
136
|
@set1 & @set2 # Nate
|
137
|
+
@set1 - @set2 # Peter
|
128
138
|
@set1 | @set2 # all 3 people
|
129
139
|
|
130
140
|
=== Values
|
131
141
|
|
132
142
|
Simple values are easy as well:
|
133
143
|
|
144
|
+
require 'redis/value'
|
134
145
|
@value = Redis::Value.new('value_name')
|
135
146
|
@value.value = 'a'
|
136
147
|
@value.delete
|
@@ -141,11 +152,11 @@ Of course complex data is no problem:
|
|
141
152
|
@newest = Redis::Value.new('newest_account')
|
142
153
|
@newest.value = @account
|
143
154
|
|
144
|
-
== Example 2: Class Usage
|
155
|
+
== Example 2: Model Class Usage
|
145
156
|
|
146
157
|
Using Redis::Objects this way makes it trivial to integrate Redis types with an
|
147
158
|
existing ActiveRecord model, DataMapper resource, or other class. Redis::Objects
|
148
|
-
will work with
|
159
|
+
will work with _any_ class that provides an +id+ method that returns a unique
|
149
160
|
value. Redis::Objects will automatically create keys that are unique to
|
150
161
|
each object.
|
151
162
|
|
@@ -185,7 +196,7 @@ Familiar Ruby array operations Just Work (TM):
|
|
185
196
|
@team.on_base.length # 1
|
186
197
|
@team.on_base.delete('player3')
|
187
198
|
|
188
|
-
Sets
|
199
|
+
Sets work too:
|
189
200
|
|
190
201
|
@team.outfielders << 'outfielder1' << 'outfielder1'
|
191
202
|
@team.outfielders << 'outfielder2'
|
@@ -195,6 +206,11 @@ Sets operations work too:
|
|
195
206
|
end
|
196
207
|
player = @team.outfielders.detect{|of| of == 'outfielder2'}
|
197
208
|
|
209
|
+
And you can do intersections between ORM objects (kinda cool):
|
210
|
+
|
211
|
+
@team1.outfielders | @team2.outfielders # all outfielders
|
212
|
+
@team1.outfielders & @team2.outfielders # should be empty
|
213
|
+
|
198
214
|
Counters can be atomically incremented/decremented (but not assigned):
|
199
215
|
|
200
216
|
@team.hits.increment # or incr
|
@@ -202,14 +218,15 @@ Counters can be atomically incremented/decremented (but not assigned):
|
|
202
218
|
@team.hits.incr(3) # add 3
|
203
219
|
@team.runs = 4 # exception
|
204
220
|
|
205
|
-
|
221
|
+
Finally, for free, you get a +redis+ handle usable in your class that
|
222
|
+
points directly to a Redis API object:
|
206
223
|
|
207
224
|
@team.redis.get('somekey')
|
208
225
|
@team.redis.smembers('someset')
|
209
226
|
|
210
|
-
You can
|
227
|
+
You can use the +redis+ handle to directly call any {Redis command}[http://code.google.com/p/redis/wiki/CommandReference]
|
211
228
|
|
212
|
-
==
|
229
|
+
== Atomic Counters and Locks
|
213
230
|
|
214
231
|
You are probably not handling atomicity correctly in your app. For a fun rant
|
215
232
|
on the topic, see
|
data/lib/redis/counter.rb
CHANGED
@@ -7,6 +7,9 @@ class Redis
|
|
7
7
|
# class to define a counter.
|
8
8
|
#
|
9
9
|
class Counter
|
10
|
+
require 'redis/helpers/core_commands'
|
11
|
+
include Redis::Helpers::CoreCommands
|
12
|
+
|
10
13
|
attr_reader :key, :options, :redis
|
11
14
|
def initialize(key, redis=$redis, options={})
|
12
15
|
@key = key
|
@@ -21,7 +24,7 @@ class Redis
|
|
21
24
|
# with a parent and starting over (for example, restarting a game and
|
22
25
|
# disconnecting all players).
|
23
26
|
def reset(to=options[:start])
|
24
|
-
redis.set
|
27
|
+
redis.set key, to.to_i
|
25
28
|
end
|
26
29
|
|
27
30
|
# Returns the current value of the counter. Normally just calling the
|
@@ -33,12 +36,6 @@ class Redis
|
|
33
36
|
end
|
34
37
|
alias_method :get, :value
|
35
38
|
|
36
|
-
# Delete a counter. Usage discouraged. Consider +reset+ instead.
|
37
|
-
def delete
|
38
|
-
redis.del(key)
|
39
|
-
end
|
40
|
-
alias_method :del, :delete
|
41
|
-
|
42
39
|
# Increment the counter atomically and return the new value. If passed
|
43
40
|
# a block, that block will be evaluated with the new value of the counter
|
44
41
|
# as an argument. If the block returns nil or throws an exception, the
|
@@ -0,0 +1,46 @@
|
|
1
|
+
class Redis
|
2
|
+
module Helpers
|
3
|
+
# These are core commands that all types share (rename, etc)
|
4
|
+
module CoreCommands
|
5
|
+
def exists?
|
6
|
+
redis.exists key
|
7
|
+
end
|
8
|
+
|
9
|
+
def delete
|
10
|
+
redis.del key
|
11
|
+
end
|
12
|
+
alias_method :del, :delete
|
13
|
+
alias_method :clear, :delete
|
14
|
+
|
15
|
+
def type
|
16
|
+
redis.type key
|
17
|
+
end
|
18
|
+
|
19
|
+
def rename(name, setkey=true)
|
20
|
+
dest = name.is_a?(self.class) ? name.key : name
|
21
|
+
ret = redis.rename key, dest
|
22
|
+
@key = dest if ret && setkey
|
23
|
+
ret
|
24
|
+
end
|
25
|
+
|
26
|
+
def renamenx(name, setkey=true)
|
27
|
+
dest = name.is_a?(self.class) ? name.key : name
|
28
|
+
ret = redis.renamenx key, dest
|
29
|
+
@key = dest if ret && setkey
|
30
|
+
ret
|
31
|
+
end
|
32
|
+
|
33
|
+
def expire(seconds)
|
34
|
+
redis.expire key, seconds
|
35
|
+
end
|
36
|
+
|
37
|
+
def expireat(unixtime)
|
38
|
+
redis.expire key, unixtime
|
39
|
+
end
|
40
|
+
|
41
|
+
def move(dbindex)
|
42
|
+
redis.move key, dbindex
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
class Redis
|
2
|
+
module Helpers
|
3
|
+
module Serialize
|
4
|
+
include Marshal
|
5
|
+
|
6
|
+
def to_redis(value)
|
7
|
+
case value
|
8
|
+
when String, Fixnum, Bignum, Float
|
9
|
+
value
|
10
|
+
else
|
11
|
+
dump(value)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def from_redis(value)
|
16
|
+
case value
|
17
|
+
when Array
|
18
|
+
value.collect{|v| from_redis(v)}
|
19
|
+
else
|
20
|
+
restore(value) rescue value
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/lib/redis/list.rb
CHANGED
@@ -6,8 +6,10 @@ class Redis
|
|
6
6
|
class List
|
7
7
|
require 'enumerator'
|
8
8
|
include Enumerable
|
9
|
-
require 'redis/
|
10
|
-
include Redis::
|
9
|
+
require 'redis/helpers/core_commands'
|
10
|
+
include Redis::Helpers::CoreCommands
|
11
|
+
require 'redis/helpers/serialize'
|
12
|
+
include Redis::Helpers::Serialize
|
11
13
|
|
12
14
|
attr_reader :key, :options, :redis
|
13
15
|
def initialize(key, redis=$redis, options={})
|
@@ -63,6 +65,7 @@ class Redis
|
|
63
65
|
|
64
66
|
# Delete the element(s) from the list that match name. If count is specified,
|
65
67
|
# only the first-N (if positive) or last-N (if negative) will be removed.
|
68
|
+
# Use .del to completely delete the entire key.
|
66
69
|
# Redis: LREM
|
67
70
|
def delete(name, count=0)
|
68
71
|
redis.lrem(key, count, name) # weird api
|
@@ -96,11 +99,6 @@ class Redis
|
|
96
99
|
at(-1)
|
97
100
|
end
|
98
101
|
|
99
|
-
# Clear the list entirely. Redis: DEL
|
100
|
-
def clear
|
101
|
-
redis.del(key)
|
102
|
-
end
|
103
|
-
|
104
102
|
# Return the length of the list. Aliased as size. Redis: LLEN
|
105
103
|
def length
|
106
104
|
redis.llen(key)
|
@@ -4,9 +4,10 @@ require 'redis/counter'
|
|
4
4
|
class Redis
|
5
5
|
module Objects
|
6
6
|
class UndefinedCounter < StandardError; end #:nodoc:
|
7
|
+
class MissingID < StandardError; end #:nodoc:
|
8
|
+
|
7
9
|
module Counters
|
8
10
|
def self.included(klass)
|
9
|
-
klass.instance_variable_set('@counters', {})
|
10
11
|
klass.instance_variable_set('@initialized_counters', {})
|
11
12
|
klass.send :include, InstanceMethods
|
12
13
|
klass.extend ClassMethods
|
@@ -14,33 +15,46 @@ class Redis
|
|
14
15
|
|
15
16
|
# Class methods that appear in your class when you include Redis::Objects.
|
16
17
|
module ClassMethods
|
17
|
-
attr_reader :
|
18
|
+
attr_reader :initialized_counters
|
18
19
|
|
19
20
|
# Define a new counter. It will function like a regular instance
|
20
21
|
# method, so it can be used alongside ActiveRecord, DataMapper, etc.
|
21
22
|
def counter(name, options={})
|
22
23
|
options[:start] ||= 0
|
23
24
|
options[:type] ||= options[:start] == 0 ? :increment : :decrement
|
24
|
-
@
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
25
|
+
@redis_objects[name] = options.merge(:type => :counter)
|
26
|
+
if options[:global]
|
27
|
+
instance_eval <<-EndMethods
|
28
|
+
def #{name}
|
29
|
+
@#{name} ||= Redis::Counter.new(field_key(:#{name}, ''), redis, @redis_objects[:#{name}])
|
30
|
+
end
|
31
|
+
EndMethods
|
32
|
+
class_eval <<-EndMethods
|
33
|
+
def #{name}
|
34
|
+
self.class.#{name}
|
35
|
+
end
|
36
|
+
EndMethods
|
37
|
+
else
|
38
|
+
class_eval <<-EndMethods
|
39
|
+
def #{name}
|
40
|
+
@#{name} ||= Redis::Counter.new(field_key(:#{name}), redis, self.class.redis_objects[:#{name}])
|
41
|
+
end
|
42
|
+
EndMethods
|
43
|
+
end
|
30
44
|
end
|
31
45
|
|
32
46
|
# Get the current value of the counter. It is more efficient
|
33
47
|
# to use the instance method if possible.
|
34
|
-
def get_counter(name, id)
|
35
|
-
verify_counter_defined!(name)
|
48
|
+
def get_counter(name, id=nil)
|
49
|
+
verify_counter_defined!(name, id)
|
36
50
|
initialize_counter!(name, id)
|
37
51
|
redis.get(field_key(name, id)).to_i
|
38
52
|
end
|
39
53
|
|
40
54
|
# Increment a counter with the specified name and id. Accepts a block
|
41
55
|
# like the instance method. See Redis::Objects::Counter for details.
|
42
|
-
def increment_counter(name, id, by=1, &block)
|
43
|
-
verify_counter_defined!(name)
|
56
|
+
def increment_counter(name, id=nil, by=1, &block)
|
57
|
+
verify_counter_defined!(name, id)
|
44
58
|
initialize_counter!(name, id)
|
45
59
|
value = redis.incr(field_key(name, id), by).to_i
|
46
60
|
block_given? ? rewindable_block(:decrement_counter, name, id, value, &block) : value
|
@@ -48,30 +62,33 @@ class Redis
|
|
48
62
|
|
49
63
|
# Decrement a counter with the specified name and id. Accepts a block
|
50
64
|
# like the instance method. See Redis::Objects::Counter for details.
|
51
|
-
def decrement_counter(name, id, by=1, &block)
|
52
|
-
verify_counter_defined!(name)
|
65
|
+
def decrement_counter(name, id=nil, by=1, &block)
|
66
|
+
verify_counter_defined!(name, id)
|
53
67
|
initialize_counter!(name, id)
|
54
68
|
value = redis.decr(field_key(name, id), by).to_i
|
55
69
|
block_given? ? rewindable_block(:increment_counter, name, id, value, &block) : value
|
56
70
|
end
|
57
71
|
|
58
72
|
# Reset a counter to its starting value.
|
59
|
-
def reset_counter(name, id, to=nil)
|
60
|
-
verify_counter_defined!(name)
|
61
|
-
to = @
|
73
|
+
def reset_counter(name, id=nil, to=nil)
|
74
|
+
verify_counter_defined!(name, id)
|
75
|
+
to = @redis_objects[name][:start] if to.nil?
|
62
76
|
redis.set(field_key(name, id), to)
|
63
77
|
end
|
64
78
|
|
65
79
|
private
|
66
80
|
|
67
|
-
def verify_counter_defined!(name) #:nodoc:
|
68
|
-
raise Redis::Objects::UndefinedCounter, "Undefined counter :#{name} for class #{self.name}" unless @
|
81
|
+
def verify_counter_defined!(name, id) #:nodoc:
|
82
|
+
raise Redis::Objects::UndefinedCounter, "Undefined counter :#{name} for class #{self.name}" unless @redis_objects.has_key?(name)
|
83
|
+
if id.nil? and !@redis_objects[name][:global]
|
84
|
+
raise Redis::Objects::MissingID, "Missing ID for non-global counter #{self.name}##{name}"
|
85
|
+
end
|
69
86
|
end
|
70
87
|
|
71
88
|
def initialize_counter!(name, id) #:nodoc:
|
72
89
|
key = field_key(name, id)
|
73
90
|
unless @initialized_counters[key]
|
74
|
-
redis.setnx(key, @
|
91
|
+
redis.setnx(key, @redis_objects[name][:start])
|
75
92
|
end
|
76
93
|
@initialized_counters[key] = true
|
77
94
|
end
|
data/lib/redis/objects/lists.rb
CHANGED
@@ -5,24 +5,34 @@ class Redis
|
|
5
5
|
module Objects
|
6
6
|
module Lists
|
7
7
|
def self.included(klass)
|
8
|
-
klass.instance_variable_set('@lists', {})
|
9
8
|
klass.send :include, InstanceMethods
|
10
9
|
klass.extend ClassMethods
|
11
10
|
end
|
12
11
|
|
13
12
|
# Class methods that appear in your class when you include Redis::Objects.
|
14
13
|
module ClassMethods
|
15
|
-
attr_reader :lists
|
16
|
-
|
17
14
|
# Define a new list. It will function like a regular instance
|
18
15
|
# method, so it can be used alongside ActiveRecord, DataMapper, etc.
|
19
16
|
def list(name, options={})
|
20
|
-
@
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
17
|
+
@redis_objects[name] = options.merge(:type => :list)
|
18
|
+
if options[:global]
|
19
|
+
instance_eval <<-EndMethods
|
20
|
+
def #{name}
|
21
|
+
@#{name} ||= Redis::List.new(field_key(:#{name}, ''), redis, @redis_objects[:#{name}])
|
22
|
+
end
|
23
|
+
EndMethods
|
24
|
+
class_eval <<-EndMethods
|
25
|
+
def #{name}
|
26
|
+
self.class.#{name}
|
27
|
+
end
|
28
|
+
EndMethods
|
29
|
+
else
|
30
|
+
class_eval <<-EndMethods
|
31
|
+
def #{name}
|
32
|
+
@#{name} ||= Redis::List.new(field_key(:#{name}), redis, self.class.redis_objects[:#{name}])
|
33
|
+
end
|
34
|
+
EndMethods
|
35
|
+
end
|
26
36
|
end
|
27
37
|
end
|
28
38
|
|
data/lib/redis/objects/locks.rb
CHANGED
@@ -6,25 +6,38 @@ class Redis
|
|
6
6
|
class UndefinedLock < StandardError; end #:nodoc:
|
7
7
|
module Locks
|
8
8
|
def self.included(klass)
|
9
|
-
klass.instance_variable_set('@locks', {})
|
10
9
|
klass.send :include, InstanceMethods
|
11
10
|
klass.extend ClassMethods
|
12
11
|
end
|
13
12
|
|
14
13
|
# Class methods that appear in your class when you include Redis::Objects.
|
15
14
|
module ClassMethods
|
16
|
-
attr_reader :locks
|
17
|
-
|
18
15
|
# Define a new lock. It will function like a model attribute,
|
19
16
|
# so it can be used alongside ActiveRecord/DataMapper, etc.
|
20
17
|
def lock(name, options={})
|
21
18
|
options[:timeout] ||= 5 # seconds
|
22
|
-
@
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
19
|
+
@redis_objects[name] = options.merge(:type => :lock)
|
20
|
+
if options[:global]
|
21
|
+
instance_eval <<-EndMethods
|
22
|
+
def #{name}_lock(&block)
|
23
|
+
@#{name} ||= Redis::Lock.new(field_key(:#{name}_lock, ''), redis, @redis_objects[:#{name}])
|
24
|
+
end
|
25
|
+
EndMethods
|
26
|
+
class_eval <<-EndMethods
|
27
|
+
def #{name}_lock(&block)
|
28
|
+
self.class.#{name}(block)
|
29
|
+
end
|
30
|
+
EndMethods
|
31
|
+
else
|
32
|
+
class_eval <<-EndMethods
|
33
|
+
def #{name}_lock(&block)
|
34
|
+
@#{name} ||= Redis::Lock.new(field_key(:#{name}_lock), redis, self.class.redis_objects[:#{name}])
|
35
|
+
end
|
36
|
+
EndMethods
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
|
28
41
|
end
|
29
42
|
|
30
43
|
# Obtain a lock, and execute the block synchronously. Any other code
|
@@ -34,7 +47,7 @@ class Redis
|
|
34
47
|
verify_lock_defined!(name)
|
35
48
|
raise ArgumentError, "Missing block to #{self.name}.obtain_lock" unless block_given?
|
36
49
|
lock_name = field_key("#{name}_lock", id)
|
37
|
-
Redis::Lock.new(redis, lock_name, self.class.
|
50
|
+
Redis::Lock.new(redis, lock_name, self.class.redis_objects[name]).lock(&block)
|
38
51
|
end
|
39
52
|
|
40
53
|
# Clear the lock. Use with care - usually only in an Admin page to clear
|
@@ -48,7 +61,7 @@ class Redis
|
|
48
61
|
private
|
49
62
|
|
50
63
|
def verify_lock_defined!(name)
|
51
|
-
raise Redis::Objects::UndefinedLock, "Undefined lock :#{name} for class #{self.name}" unless @
|
64
|
+
raise Redis::Objects::UndefinedLock, "Undefined lock :#{name} for class #{self.name}" unless @redis_objects.has_key?(name)
|
52
65
|
end
|
53
66
|
end
|
54
67
|
end
|
data/lib/redis/objects/sets.rb
CHANGED
@@ -5,24 +5,35 @@ class Redis
|
|
5
5
|
module Objects
|
6
6
|
module Sets
|
7
7
|
def self.included(klass)
|
8
|
-
klass.instance_variable_set('@sets', {})
|
9
8
|
klass.send :include, InstanceMethods
|
10
9
|
klass.extend ClassMethods
|
11
10
|
end
|
12
11
|
|
13
12
|
# Class methods that appear in your class when you include Redis::Objects.
|
14
13
|
module ClassMethods
|
15
|
-
attr_reader :sets
|
16
|
-
|
17
14
|
# Define a new list. It will function like a regular instance
|
18
15
|
# method, so it can be used alongside ActiveRecord, DataMapper, etc.
|
19
16
|
def set(name, options={})
|
20
|
-
@
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
17
|
+
@redis_objects[name] = options.merge(:type => :set)
|
18
|
+
if options[:global]
|
19
|
+
instance_eval <<-EndMethods
|
20
|
+
def #{name}
|
21
|
+
@#{name} ||= Redis::Set.new(field_key(:#{name}, ''), redis, @redis_objects[:#{name}])
|
22
|
+
end
|
23
|
+
EndMethods
|
24
|
+
class_eval <<-EndMethods
|
25
|
+
def #{name}
|
26
|
+
self.class.#{name}
|
27
|
+
end
|
28
|
+
EndMethods
|
29
|
+
else
|
30
|
+
class_eval <<-EndMethods
|
31
|
+
def #{name}
|
32
|
+
@#{name} ||= Redis::Set.new(field_key(:#{name}), redis, self.class.redis_objects[:#{name}])
|
33
|
+
end
|
34
|
+
EndMethods
|
35
|
+
end
|
36
|
+
|
26
37
|
end
|
27
38
|
end
|
28
39
|
|
data/lib/redis/objects/values.rb
CHANGED
@@ -5,27 +5,44 @@ class Redis
|
|
5
5
|
module Objects
|
6
6
|
module Values
|
7
7
|
def self.included(klass)
|
8
|
-
klass.instance_variable_set('@values', {})
|
9
8
|
klass.send :include, InstanceMethods
|
10
9
|
klass.extend ClassMethods
|
11
10
|
end
|
12
11
|
|
13
12
|
# Class methods that appear in your class when you include Redis::Objects.
|
14
13
|
module ClassMethods
|
15
|
-
attr_reader :values
|
16
|
-
|
17
14
|
# Define a new simple value. It will function like a regular instance
|
18
15
|
# method, so it can be used alongside ActiveRecord, DataMapper, etc.
|
19
16
|
def value(name, options={})
|
20
|
-
@
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
#{name}
|
27
|
-
|
28
|
-
|
17
|
+
@redis_objects[name] = options.merge(:type => :value)
|
18
|
+
if options[:global]
|
19
|
+
instance_eval <<-EndMethods
|
20
|
+
def #{name}
|
21
|
+
@#{name} ||= Redis::Value.new(field_key(:#{name}, ''), redis, @redis_objects[:#{name}])
|
22
|
+
end
|
23
|
+
def #{name}=(value)
|
24
|
+
#{name}.value = value
|
25
|
+
end
|
26
|
+
EndMethods
|
27
|
+
class_eval <<-EndMethods
|
28
|
+
def #{name}
|
29
|
+
self.class.#{name}
|
30
|
+
end
|
31
|
+
def #{name}=(value)
|
32
|
+
self.class.#{name} = value
|
33
|
+
end
|
34
|
+
EndMethods
|
35
|
+
else
|
36
|
+
class_eval <<-EndMethods
|
37
|
+
def #{name}
|
38
|
+
@#{name} ||= Redis::Value.new(field_key(:#{name}), redis, self.class.redis_objects[:#{name}])
|
39
|
+
end
|
40
|
+
def #{name}=(value)
|
41
|
+
#{name}.value = value
|
42
|
+
end
|
43
|
+
EndMethods
|
44
|
+
end
|
45
|
+
|
29
46
|
end
|
30
47
|
end
|
31
48
|
|
data/lib/redis/objects.rb
CHANGED
@@ -55,6 +55,7 @@ class Redis
|
|
55
55
|
def included(klass)
|
56
56
|
# Core (this file)
|
57
57
|
klass.instance_variable_set('@redis', @redis)
|
58
|
+
klass.instance_variable_set('@redis_objects', {})
|
58
59
|
klass.send :include, InstanceMethods
|
59
60
|
klass.extend ClassMethods
|
60
61
|
|
@@ -69,8 +70,7 @@ class Redis
|
|
69
70
|
|
70
71
|
# Class methods that appear in your class when you include Redis::Objects.
|
71
72
|
module ClassMethods
|
72
|
-
attr_accessor :redis
|
73
|
-
|
73
|
+
attr_accessor :redis, :redis_objects
|
74
74
|
|
75
75
|
# Set the Redis prefix to use. Defaults to model_name
|
76
76
|
def prefix=(prefix) @prefix = prefix end
|
data/lib/redis/set.rb
CHANGED
@@ -5,8 +5,10 @@ class Redis
|
|
5
5
|
class Set
|
6
6
|
require 'enumerator'
|
7
7
|
include Enumerable
|
8
|
-
require 'redis/
|
9
|
-
include Redis::
|
8
|
+
require 'redis/helpers/core_commands'
|
9
|
+
include Redis::Helpers::CoreCommands
|
10
|
+
require 'redis/helpers/serialize'
|
11
|
+
include Redis::Helpers::Serialize
|
10
12
|
|
11
13
|
attr_reader :key, :options, :redis
|
12
14
|
|
@@ -46,11 +48,6 @@ class Redis
|
|
46
48
|
redis.srem(key, value)
|
47
49
|
end
|
48
50
|
|
49
|
-
# Wipe the set entirely. Redis: DEL
|
50
|
-
def clear
|
51
|
-
redis.del(key)
|
52
|
-
end
|
53
|
-
|
54
51
|
# Iterate through each member of the set. Redis::Objects mixes in Enumerable,
|
55
52
|
# so you can also use familiar methods like +collect+, +detect+, and so forth.
|
56
53
|
def each(&block)
|
@@ -99,11 +96,36 @@ class Redis
|
|
99
96
|
alias_method :+, :union
|
100
97
|
|
101
98
|
# Calculate the union and store it in Redis as +name+. Returns the number
|
102
|
-
# of elements in the stored union. Redis:
|
99
|
+
# of elements in the stored union. Redis: SUNIONSTORE
|
103
100
|
def unionstore(name, *sets)
|
104
101
|
redis.sunionstore(name, key, *keys_from_objects(sets))
|
105
102
|
end
|
106
103
|
|
104
|
+
# Return the difference vs another set. Can pass it either another set
|
105
|
+
# object or set name. Also available as ^ or - which is a bit cleaner:
|
106
|
+
#
|
107
|
+
# members_difference = set1 ^ set2
|
108
|
+
# members_difference = set1 - set2
|
109
|
+
#
|
110
|
+
# If you want to specify multiple sets, you must use +difference+:
|
111
|
+
#
|
112
|
+
# members_difference = set1.difference(set2, set3, set4)
|
113
|
+
# members_difference = set1.diff(set2, set3, set4)
|
114
|
+
#
|
115
|
+
# Redis: SDIFF
|
116
|
+
def difference(*sets)
|
117
|
+
from_redis redis.sdiff(key, *keys_from_objects(sets))
|
118
|
+
end
|
119
|
+
alias_method :diff, :difference
|
120
|
+
alias_method :^, :difference
|
121
|
+
alias_method :-, :difference
|
122
|
+
|
123
|
+
# Calculate the diff and store it in Redis as +name+. Returns the number
|
124
|
+
# of elements in the stored union. Redis: SDIFFSTORE
|
125
|
+
def diffstore(name, *sets)
|
126
|
+
redis.sdiffstore(name, key, *keys_from_objects(sets))
|
127
|
+
end
|
128
|
+
|
107
129
|
# The number of members in the set. Aliased as size. Redis: SCARD
|
108
130
|
def length
|
109
131
|
redis.scard(key)
|
data/lib/redis/value.rb
CHANGED
@@ -3,8 +3,10 @@ class Redis
|
|
3
3
|
# Class representing a simple value. You can use standard Ruby operations on it.
|
4
4
|
#
|
5
5
|
class Value
|
6
|
-
require 'redis/
|
7
|
-
include Redis::
|
6
|
+
require 'redis/helpers/core_commands'
|
7
|
+
include Redis::Helpers::CoreCommands
|
8
|
+
require 'redis/helpers/serialize'
|
9
|
+
include Redis::Helpers::Serialize
|
8
10
|
|
9
11
|
attr_reader :key, :options, :redis
|
10
12
|
def initialize(key, redis=$redis, options={})
|
@@ -15,19 +17,15 @@ class Redis
|
|
15
17
|
end
|
16
18
|
|
17
19
|
def value=(val)
|
18
|
-
redis.set
|
20
|
+
redis.set key, to_redis(val)
|
19
21
|
end
|
20
|
-
|
22
|
+
alias_method :set, :value=
|
23
|
+
|
21
24
|
def value
|
22
25
|
from_redis redis.get(key)
|
23
26
|
end
|
24
27
|
alias_method :get, :value
|
25
28
|
|
26
|
-
def delete
|
27
|
-
redis.del(key)
|
28
|
-
end
|
29
|
-
alias_method :del, :delete
|
30
|
-
|
31
29
|
def to_s; value.to_s; end
|
32
30
|
alias_method :to_str, :to_s
|
33
31
|
|
@@ -36,6 +36,18 @@ describe Redis::Value do
|
|
36
36
|
@value.should be_nil
|
37
37
|
end
|
38
38
|
|
39
|
+
it "should support renaming values" do
|
40
|
+
@value.value = 'Peter Pan'
|
41
|
+
@value.key.should == 'spec/value'
|
42
|
+
@value.rename('spec/value2').should be_true
|
43
|
+
@value.key.should == 'spec/value2'
|
44
|
+
@value.should == 'Peter Pan'
|
45
|
+
old = Redis::Value.new('spec/value')
|
46
|
+
old.should be_nil
|
47
|
+
old.value = 'Tuff'
|
48
|
+
@value.renamenx('spec/value').should be_false
|
49
|
+
end
|
50
|
+
|
39
51
|
after :all do
|
40
52
|
@value.delete
|
41
53
|
end
|
@@ -131,6 +143,28 @@ describe Redis::List do
|
|
131
143
|
@list.last.should == [1,2,3,[4,5]]
|
132
144
|
@list.shift.should == {:json => 'data'}
|
133
145
|
end
|
146
|
+
|
147
|
+
it "should support renaming lists" do
|
148
|
+
@list.should be_empty
|
149
|
+
@list << 'a' << 'b' << 'a' << 3
|
150
|
+
@list.should == ['a','b','a','3']
|
151
|
+
@list.key.should == 'spec/list'
|
152
|
+
@list.rename('spec/list3', false).should be_true
|
153
|
+
@list.key.should == 'spec/list'
|
154
|
+
@list.redis.del('spec/list3')
|
155
|
+
@list << 'a' << 'b' << 'a' << 3
|
156
|
+
@list.rename('spec/list2').should be_true
|
157
|
+
@list.key.should == 'spec/list2'
|
158
|
+
@list.redis.lrange(@list.key, 0, 3).should == ['a','b','a','3']
|
159
|
+
old = Redis::List.new('spec/list')
|
160
|
+
old.should be_empty
|
161
|
+
old << 'Tuff'
|
162
|
+
@list.renamenx('spec/list').should be_false
|
163
|
+
@list.renamenx(old).should be_false
|
164
|
+
@list.renamenx('spec/foo').should be_true
|
165
|
+
@list.clear
|
166
|
+
@list.redis.del('spec/list2')
|
167
|
+
end
|
134
168
|
|
135
169
|
after :all do
|
136
170
|
@list.clear
|
@@ -191,7 +225,7 @@ describe Redis::Set do
|
|
191
225
|
@set.sort.should == ['a','b','c']
|
192
226
|
end
|
193
227
|
|
194
|
-
it "should handle set intersections and
|
228
|
+
it "should handle set intersections, unions, and diffs" do
|
195
229
|
@set_1 << 'a' << 'b' << 'c' << 'd' << 'e'
|
196
230
|
@set_2 << 'c' << 'd' << 'e' << 'f' << 'g'
|
197
231
|
@set_3 << 'a' << 'd' << 'g' << 'l' << 'm'
|
@@ -216,6 +250,34 @@ describe Redis::Set do
|
|
216
250
|
@set_1.redis.smembers(UNIONSTORE_KEY).sort.should == ['a','b','c','d','e','f','g']
|
217
251
|
@set_1.unionstore(UNIONSTORE_KEY, @set_2, @set_3).should == 9
|
218
252
|
@set_1.redis.smembers(UNIONSTORE_KEY).sort.should == ['a','b','c','d','e','f','g','l','m']
|
253
|
+
|
254
|
+
(@set_1 ^ @set_2).sort.should == ["a", "b"]
|
255
|
+
(@set_1 - @set_2).sort.should == ["a", "b"]
|
256
|
+
(@set_2 - @set_1).sort.should == ["f", "g"]
|
257
|
+
@set_1.difference(@set_2).sort.should == ["a", "b"]
|
258
|
+
@set_1.diff(@set_2).sort.should == ["a", "b"]
|
259
|
+
@set_1.difference(@set_2, @set_3).sort.should == ['b']
|
260
|
+
@set_1.diffstore(DIFFSTORE_KEY, @set_2).should == 2
|
261
|
+
@set_1.redis.smembers(DIFFSTORE_KEY).sort.should == ['a','b']
|
262
|
+
@set_1.diffstore(DIFFSTORE_KEY, @set_2, @set_3).should == 1
|
263
|
+
@set_1.redis.smembers(DIFFSTORE_KEY).sort.should == ['b']
|
264
|
+
end
|
265
|
+
|
266
|
+
it "should support renaming sets" do
|
267
|
+
@set.should be_empty
|
268
|
+
@set << 'a' << 'b' << 'a' << 3
|
269
|
+
@set.sort.should == ['3','a','b']
|
270
|
+
@set.key.should == 'spec/set'
|
271
|
+
@set.rename('spec/set2').should be_true
|
272
|
+
@set.key.should == 'spec/set2'
|
273
|
+
old = Redis::Set.new('spec/set')
|
274
|
+
old.should be_empty
|
275
|
+
old << 'Tuff'
|
276
|
+
@set.renamenx('spec/set').should be_false
|
277
|
+
@set.renamenx(old).should be_false
|
278
|
+
@set.renamenx('spec/foo').should be_true
|
279
|
+
@set.clear
|
280
|
+
@set.redis.del('spec/set2')
|
219
281
|
end
|
220
282
|
|
221
283
|
after :all do
|
@@ -9,12 +9,17 @@ class Roster
|
|
9
9
|
counter :available_slots, :start => 10
|
10
10
|
counter :pitchers, :limit => :max_pitchers
|
11
11
|
counter :basic
|
12
|
-
counter :all_players_online, :global => true
|
13
12
|
lock :resort, :timeout => 2
|
14
13
|
value :starting_pitcher
|
15
14
|
list :player_stats
|
16
15
|
set :outfielders
|
17
16
|
|
17
|
+
# global class counters
|
18
|
+
counter :total_players_online, :global => true
|
19
|
+
list :all_player_stats, :global => true
|
20
|
+
set :all_players_online, :global => true
|
21
|
+
value :last_player, :global => true
|
22
|
+
|
18
23
|
def initialize(id=1) @id = id end
|
19
24
|
def id; @id; end
|
20
25
|
def max_pitchers; 3; end
|
@@ -43,6 +48,12 @@ describe Redis::Objects do
|
|
43
48
|
@roster_3.outfielders.clear
|
44
49
|
@roster.redis.del(UNIONSTORE_KEY)
|
45
50
|
@roster.redis.del(INTERSTORE_KEY)
|
51
|
+
@roster.redis.del(DIFFSTORE_KEY)
|
52
|
+
|
53
|
+
Roster.total_players_online.reset
|
54
|
+
Roster.all_player_stats.clear
|
55
|
+
Roster.all_players_online.clear
|
56
|
+
Roster.last_player.delete
|
46
57
|
end
|
47
58
|
|
48
59
|
it "should provide a connection method" do
|
@@ -97,6 +108,24 @@ describe Redis::Objects do
|
|
97
108
|
Roster.get_counter(:available_slots, @roster.id).should == 10
|
98
109
|
end
|
99
110
|
|
111
|
+
it "should support class-level increment/decrement of global counters" do
|
112
|
+
Roster.total_players_online.should == 0
|
113
|
+
Roster.total_players_online.increment.should == 1
|
114
|
+
Roster.total_players_online.decrement.should == 0
|
115
|
+
Roster.total_players_online.increment(3).should == 3
|
116
|
+
Roster.total_players_online.decrement(2).should == 1
|
117
|
+
Roster.total_players_online.reset.should be_true
|
118
|
+
Roster.total_players_online.should == 0
|
119
|
+
|
120
|
+
Roster.get_counter(:total_players_online).should == 0
|
121
|
+
Roster.increment_counter(:total_players_online).should == 1
|
122
|
+
Roster.increment_counter(:total_players_online, nil, 3).should == 4
|
123
|
+
Roster.decrement_counter(:total_players_online, nil, 2).should == 2
|
124
|
+
Roster.decrement_counter(:total_players_online).should == 1
|
125
|
+
Roster.reset_counter(:total_players_online).should == true
|
126
|
+
Roster.get_counter(:total_players_online).should == 0
|
127
|
+
end
|
128
|
+
|
100
129
|
it "should take an atomic block for increment/decrement" do
|
101
130
|
a = false
|
102
131
|
@roster.available_slots.should == 10
|
@@ -259,6 +288,8 @@ describe Redis::Objects do
|
|
259
288
|
@roster.starting_pitcher = 'Trevor Hoffman'
|
260
289
|
@roster.starting_pitcher.should == 'Trevor Hoffman'
|
261
290
|
@roster.starting_pitcher.get.should == 'Trevor Hoffman'
|
291
|
+
@roster.starting_pitcher = 'Tom Selleck'
|
292
|
+
@roster.starting_pitcher.should == 'Tom Selleck'
|
262
293
|
@roster.starting_pitcher.del.should be_true
|
263
294
|
@roster.starting_pitcher.should be_nil
|
264
295
|
end
|
@@ -409,6 +440,162 @@ describe Redis::Objects do
|
|
409
440
|
@roster_1.redis.smembers(UNIONSTORE_KEY).sort.should == ['a','b','c','d','e','f','g','l','m']
|
410
441
|
end
|
411
442
|
|
443
|
+
it "should handle class-level global lists of simple values" do
|
444
|
+
Roster.all_player_stats.should be_empty
|
445
|
+
Roster.all_player_stats << 'a'
|
446
|
+
Roster.all_player_stats.should == ['a']
|
447
|
+
Roster.all_player_stats.get.should == ['a']
|
448
|
+
Roster.all_player_stats.unshift 'b'
|
449
|
+
Roster.all_player_stats.to_s.should == 'b, a'
|
450
|
+
Roster.all_player_stats.should == ['b','a']
|
451
|
+
Roster.all_player_stats.get.should == ['b','a']
|
452
|
+
Roster.all_player_stats.push 'c'
|
453
|
+
Roster.all_player_stats.should == ['b','a','c']
|
454
|
+
Roster.all_player_stats.get.should == ['b','a','c']
|
455
|
+
Roster.all_player_stats.first.should == 'b'
|
456
|
+
Roster.all_player_stats.last.should == 'c'
|
457
|
+
Roster.all_player_stats << 'd'
|
458
|
+
Roster.all_player_stats.should == ['b','a','c','d']
|
459
|
+
Roster.all_player_stats[1].should == 'a'
|
460
|
+
Roster.all_player_stats[0].should == 'b'
|
461
|
+
Roster.all_player_stats[2].should == 'c'
|
462
|
+
Roster.all_player_stats[3].should == 'd'
|
463
|
+
Roster.all_player_stats.include?('c').should be_true
|
464
|
+
Roster.all_player_stats.include?('no').should be_false
|
465
|
+
Roster.all_player_stats.pop.should == 'd'
|
466
|
+
Roster.all_player_stats[0].should == Roster.all_player_stats.at(0)
|
467
|
+
Roster.all_player_stats[1].should == Roster.all_player_stats.at(1)
|
468
|
+
Roster.all_player_stats[2].should == Roster.all_player_stats.at(2)
|
469
|
+
Roster.all_player_stats.should == ['b','a','c']
|
470
|
+
Roster.all_player_stats.get.should == ['b','a','c']
|
471
|
+
Roster.all_player_stats.shift.should == 'b'
|
472
|
+
Roster.all_player_stats.should == ['a','c']
|
473
|
+
Roster.all_player_stats.get.should == ['a','c']
|
474
|
+
Roster.all_player_stats << 'e' << 'f' << 'e'
|
475
|
+
Roster.all_player_stats.should == ['a','c','e','f','e']
|
476
|
+
Roster.all_player_stats.get.should == ['a','c','e','f','e']
|
477
|
+
Roster.all_player_stats.delete('e').should == 2
|
478
|
+
Roster.all_player_stats.should == ['a','c','f']
|
479
|
+
Roster.all_player_stats.get.should == ['a','c','f']
|
480
|
+
Roster.all_player_stats << 'j'
|
481
|
+
Roster.all_player_stats.should == ['a','c','f','j']
|
482
|
+
Roster.all_player_stats[0..2].should == ['a','c','f']
|
483
|
+
Roster.all_player_stats[1, 3].should == ['c','f','j']
|
484
|
+
Roster.all_player_stats.length.should == 4
|
485
|
+
Roster.all_player_stats.size.should == 4
|
486
|
+
Roster.all_player_stats.should == ['a','c','f','j']
|
487
|
+
Roster.all_player_stats.get.should == ['a','c','f','j']
|
488
|
+
|
489
|
+
i = -1
|
490
|
+
Roster.all_player_stats.each do |st|
|
491
|
+
st.should == Roster.all_player_stats[i += 1]
|
492
|
+
end
|
493
|
+
Roster.all_player_stats.should == ['a','c','f','j']
|
494
|
+
Roster.all_player_stats.get.should == ['a','c','f','j']
|
495
|
+
|
496
|
+
Roster.all_player_stats.each_with_index do |st,i|
|
497
|
+
st.should == Roster.all_player_stats[i]
|
498
|
+
end
|
499
|
+
Roster.all_player_stats.should == ['a','c','f','j']
|
500
|
+
Roster.all_player_stats.get.should == ['a','c','f','j']
|
501
|
+
|
502
|
+
coll = Roster.all_player_stats.collect{|st| st}
|
503
|
+
coll.should == ['a','c','f','j']
|
504
|
+
Roster.all_player_stats.should == ['a','c','f','j']
|
505
|
+
Roster.all_player_stats.get.should == ['a','c','f','j']
|
506
|
+
|
507
|
+
Roster.all_player_stats << 'a'
|
508
|
+
coll = Roster.all_player_stats.select{|st| st == 'a'}
|
509
|
+
coll.should == ['a','a']
|
510
|
+
Roster.all_player_stats.should == ['a','c','f','j','a']
|
511
|
+
Roster.all_player_stats.get.should == ['a','c','f','j','a']
|
512
|
+
end
|
513
|
+
|
514
|
+
it "should handle class-level global sets of simple values" do
|
515
|
+
Roster.all_players_online.should be_empty
|
516
|
+
Roster.all_players_online << 'a' << 'a' << 'a'
|
517
|
+
Roster.all_players_online.should == ['a']
|
518
|
+
Roster.all_players_online.get.should == ['a']
|
519
|
+
Roster.all_players_online << 'b' << 'b'
|
520
|
+
Roster.all_players_online.to_s.should == 'a, b'
|
521
|
+
Roster.all_players_online.should == ['a','b']
|
522
|
+
Roster.all_players_online.members.should == ['a','b']
|
523
|
+
Roster.all_players_online.get.should == ['a','b']
|
524
|
+
Roster.all_players_online << 'c'
|
525
|
+
Roster.all_players_online.sort.should == ['a','b','c']
|
526
|
+
Roster.all_players_online.get.sort.should == ['a','b','c']
|
527
|
+
Roster.all_players_online.delete('c')
|
528
|
+
Roster.all_players_online.should == ['a','b']
|
529
|
+
Roster.all_players_online.get.sort.should == ['a','b']
|
530
|
+
Roster.all_players_online.length.should == 2
|
531
|
+
Roster.all_players_online.size.should == 2
|
532
|
+
|
533
|
+
i = 0
|
534
|
+
Roster.all_players_online.each do |st|
|
535
|
+
i += 1
|
536
|
+
end
|
537
|
+
i.should == Roster.all_players_online.length
|
538
|
+
|
539
|
+
coll = Roster.all_players_online.collect{|st| st}
|
540
|
+
coll.should == ['a','b']
|
541
|
+
Roster.all_players_online.should == ['a','b']
|
542
|
+
Roster.all_players_online.get.should == ['a','b']
|
543
|
+
|
544
|
+
Roster.all_players_online << 'c'
|
545
|
+
Roster.all_players_online.member?('c').should be_true
|
546
|
+
Roster.all_players_online.include?('c').should be_true
|
547
|
+
Roster.all_players_online.member?('no').should be_false
|
548
|
+
coll = Roster.all_players_online.select{|st| st == 'c'}
|
549
|
+
coll.should == ['c']
|
550
|
+
Roster.all_players_online.sort.should == ['a','b','c']
|
551
|
+
end
|
552
|
+
|
553
|
+
it "should handle class-level global values" do
|
554
|
+
Roster.last_player.should == nil
|
555
|
+
Roster.last_player = 'Trevor Hoffman'
|
556
|
+
Roster.last_player.should == 'Trevor Hoffman'
|
557
|
+
Roster.last_player.get.should == 'Trevor Hoffman'
|
558
|
+
Roster.last_player = 'Tom Selleck'
|
559
|
+
Roster.last_player.should == 'Tom Selleck'
|
560
|
+
Roster.last_player.del.should be_true
|
561
|
+
Roster.last_player.should be_nil
|
562
|
+
end
|
563
|
+
|
564
|
+
it "should easily enable @object.class.global_objects" do
|
565
|
+
@roster.class.all_players_online.should be_empty
|
566
|
+
@roster.class.all_players_online << 'a' << 'a' << 'a'
|
567
|
+
@roster.class.all_players_online.should == ['a']
|
568
|
+
@roster2.class.all_players_online.should == ['a']
|
569
|
+
|
570
|
+
@roster.all_players_online.should == ['a']
|
571
|
+
@roster2.all_players_online.should == ['a']
|
572
|
+
|
573
|
+
@roster.class.all_player_stats.should be_empty
|
574
|
+
@roster.class.all_player_stats << 'a'
|
575
|
+
@roster.class.all_player_stats.should == ['a']
|
576
|
+
@roster.class.all_player_stats.get.should == ['a']
|
577
|
+
@roster.class.all_player_stats.unshift 'b'
|
578
|
+
@roster.class.all_player_stats.to_s.should == 'b, a'
|
579
|
+
@roster.class.all_player_stats.should == ['b','a']
|
580
|
+
@roster2.class.all_player_stats.should == ['b','a']
|
581
|
+
|
582
|
+
@roster.all_player_stats.should == ['b','a']
|
583
|
+
@roster2.all_player_stats.should == ['b','a']
|
584
|
+
@roster2.all_player_stats << 'b'
|
585
|
+
@roster.all_player_stats.should == ['b','a','b']
|
586
|
+
|
587
|
+
@roster.last_player.should == nil
|
588
|
+
@roster.class.last_player = 'Trevor Hoffman'
|
589
|
+
@roster.last_player.should == 'Trevor Hoffman'
|
590
|
+
@roster.last_player.get.should == 'Trevor Hoffman'
|
591
|
+
@roster2.last_player.get.should == 'Trevor Hoffman'
|
592
|
+
@roster2.last_player = 'Tom Selleck'
|
593
|
+
@roster.last_player.should == 'Tom Selleck'
|
594
|
+
@roster.last_player.del.should be_true
|
595
|
+
@roster.last_player.should be_nil
|
596
|
+
@roster2.last_player.should be_nil
|
597
|
+
end
|
598
|
+
|
412
599
|
it "should handle lists of complex data types" do
|
413
600
|
@roster.player_stats << {:json => 'data'}
|
414
601
|
@roster.player_stats << {:json2 => 'data2'}
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: redis-objects
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nate Wiger
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-
|
12
|
+
date: 2009-12-14 00:00:00 -08:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -30,25 +30,27 @@ extensions: []
|
|
30
30
|
|
31
31
|
extra_rdoc_files:
|
32
32
|
- ATOMICITY.rdoc
|
33
|
+
- ChangeLog
|
33
34
|
- README.rdoc
|
34
35
|
files:
|
35
36
|
- lib/redis/counter.rb
|
37
|
+
- lib/redis/helpers/core_commands.rb
|
38
|
+
- lib/redis/helpers/serialize.rb
|
36
39
|
- lib/redis/list.rb
|
37
40
|
- lib/redis/lock.rb
|
38
|
-
- lib/redis/objects/core.rb
|
39
41
|
- lib/redis/objects/counters.rb
|
40
42
|
- lib/redis/objects/lists.rb
|
41
43
|
- lib/redis/objects/locks.rb
|
42
44
|
- lib/redis/objects/sets.rb
|
43
45
|
- lib/redis/objects/values.rb
|
44
46
|
- lib/redis/objects.rb
|
45
|
-
- lib/redis/serialize.rb
|
46
47
|
- lib/redis/set.rb
|
47
48
|
- lib/redis/value.rb
|
48
49
|
- spec/redis_objects_instance_spec.rb
|
49
50
|
- spec/redis_objects_model_spec.rb
|
50
51
|
- spec/spec_helper.rb
|
51
52
|
- ATOMICITY.rdoc
|
53
|
+
- ChangeLog
|
52
54
|
- README.rdoc
|
53
55
|
has_rdoc: true
|
54
56
|
homepage: http://github.com/nateware/redis-objects
|
data/lib/redis/objects/core.rb
DELETED
File without changes
|
data/lib/redis/serialize.rb
DELETED
@@ -1,23 +0,0 @@
|
|
1
|
-
class Redis
|
2
|
-
module Serialize
|
3
|
-
include Marshal
|
4
|
-
|
5
|
-
def to_redis(value)
|
6
|
-
case value
|
7
|
-
when String, Fixnum, Bignum, Float
|
8
|
-
value
|
9
|
-
else
|
10
|
-
dump(value)
|
11
|
-
end
|
12
|
-
end
|
13
|
-
|
14
|
-
def from_redis(value)
|
15
|
-
case value
|
16
|
-
when Array
|
17
|
-
value.collect{|v| from_redis(v)}
|
18
|
-
else
|
19
|
-
restore(value) rescue value
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|