redis-objects 0.2.1 → 0.2.2
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/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
|