redis-objects 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README.rdoc CHANGED
@@ -1,85 +1,192 @@
1
- = Redis::Objects - Lightweight, atomic object layer around redis-rb
1
+ = Redis::Objects - Map Redis types directly to Ruby objects
2
2
 
3
3
  This is *not* an ORM. People that are wrapping ORM's around Redis are missing
4
4
  the point.
5
5
 
6
6
  The killer feature of Redis that it allows you to perform atomic operations
7
- on _individual_ data structures, like counters, lists, and sets, that you can use
8
- *with* your existing ActiveRecord/DataMapper/etc models, or in classes that have
9
- nothing to do with an ORM or even a database. That's where this gem comes in.
7
+ on _individual_ data structures, like counters, lists, and sets. You can then use
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 Ezra's +redis+ library
10
+ to Ruby objects to make use seamless.
10
11
 
11
-
12
- This gem originally arose out of a need for high-concurrency operations for online
13
- games; for a fun rant on the topic, see
12
+ This gem originally arose out of a need for high-concurrency atomic operations;
13
+ for a fun rant on the topic, see
14
14
  {ATOMICITY}[http://github.com/nateware/redis-objects/blob/master/ATOMICITY.rdoc],
15
15
  or scroll down to "Atomicity" in this README.
16
16
 
17
- There are two ways to use Redis::Objects, either as an +include+ in a class, or
18
- by using +new+ with the type of data structure you want to create.
17
+ There are two ways to use Redis::Objects, either as an +include+ in a model class,
18
+ or by using +new+ with the type of data structure you want to create.
19
19
 
20
20
  == Installation
21
21
 
22
22
  gem install gemcutter
23
23
  gem tumble
24
24
  gem install redis-objects
25
-
25
+
26
+ == Example 1: Individual Usage
27
+
28
+ There is a Ruby object that maps to each Redis type.
29
+
26
30
  === Initialization
27
31
 
28
- # If on Rails, config/initializers/redis.rb is a good place for this
32
+ You can either set the $redis global variable to your Redis handle:
33
+
34
+ $redis = Redis.new(:host => 'localhost', :port => 6379)
35
+ @value = Redis::Value.new('myvalue')
36
+
37
+ Or you can pass the Redis handle into the new method:
38
+
39
+ redis = Redis.new(:host => 'localhost', :port => 6379)
40
+ @value = Redis::Value.new('myvalue', redis)
41
+
42
+ === Counters
43
+
44
+ Create a new counter. The +counter_name+ is the key stored in Redis.
45
+
46
+ @counter = Redis::Counter.new('counter_name')
47
+ @counter.increment
48
+ @counter.decrement
49
+ puts @counter.value
50
+ @counter.increment do |val|
51
+ raise "Full" if val > MAX_VAL # rewind counter
52
+ end
53
+
54
+ See the section on "Atomicity" for cool uses of atomic counter blocks.
55
+
56
+ === Lists
57
+
58
+ Lists work just like Ruby arrays:
59
+
60
+ @list = Redis::List.new('list_name')
61
+ @list << 'a'
62
+ @list << 'b'
63
+ @list.include? 'c' # false
64
+ @list.values # ['a','b']
65
+ @list << 'c'
66
+ @list.delete('c')
67
+ @list[0]
68
+ @list[0,1]
69
+ @list[0..1]
70
+ @list.shift
71
+ @list.pop
72
+ @list.clear
73
+ # etc
74
+
75
+ === Sets
76
+
77
+ Sets work like the Ruby {Set}[http://ruby-doc.org/core/classes/Set.html] class:
78
+
79
+ @set = Redis::Set.new('set_name')
80
+ @set << 'a'
81
+ @set << 'b'
82
+ @set << 'a' # dup ignored
83
+ @set.member? 'c' # false
84
+ @set.members # ['a','b']
85
+ @set.each do |member|
86
+ puts member
87
+ end
88
+ @set.clear
89
+ # etc
90
+
91
+ You can perform Redis intersections/unions easily:
92
+
93
+ @set1 = Redis::Set.new('set1')
94
+ @set2 = Redis::Set.new('set2')
95
+ @set3 = Redis::Set.new('set3')
96
+ members = @set1 & @set2 # intersection
97
+ members = @set1 | @set2 # union
98
+ members = @set1 + @set2 # union
99
+ members = @set1.intersection(@set2, @set3) # multiple
100
+ members = @set1.union(@set2, @set3) # multiple
101
+
102
+ Or store them in Redis:
103
+
104
+ @set1.interstore('intername', @set2, @set3)
105
+ members = @set1.redis.get('intername')
106
+ @set1.unionstore('unionname', @set2, @set3)
107
+ members = @set1.redis.get('unionname')
108
+
109
+ === Values
110
+
111
+ Simple values are easy as well:
112
+
113
+ @value = Redis::Value.new('value_name')
114
+ @value.value = 'a'
115
+ @value.delete
116
+
117
+ == Example 2: Class Usage
118
+
119
+ This method of use makes it easy to integrate Redis types with an existing
120
+ ActiveRecord model, DataMapper resource, or other class. Redis::Objects
121
+ will work with any class that provides an +id+ method that returns a unique
122
+ value. Redis::Objects will automatically create keys that are unique to
123
+ each object.
124
+
125
+ === Initialization
126
+
127
+ If on Rails, config/initializers/redis.rb is a good place for this:
128
+
29
129
  require 'redis'
30
130
  require 'redis/objects'
31
131
  Redis::Objects.redis = Redis.new(:host => 127.0.0.1, :port => 6379)
32
132
 
33
- == Examples
133
+ === Model Class
34
134
 
35
- === Model Class Usage
135
+ Include Redis::Objects in any type of class:
36
136
 
37
137
  class Team < ActiveRecord::Base
38
138
  include Redis::Objects
39
139
 
40
- counter :drafted_players
41
- counter :active_players
42
- counter :total_online_players, :global => true
140
+ counter :hits
141
+ counter :runs
142
+ counter :outs
143
+ counter :inning, :start => 1
43
144
  list :on_base
44
145
  set :outfielders
45
- value :coach
146
+ value :at_bat
46
147
  end
47
-
48
- Familiar Ruby operations Just Work:
49
148
 
50
- @team = Team.find(1)
149
+ Familiar Ruby array operations Just Work (TM):
150
+
151
+ @team = Team.find_by_name('New York Yankees')
51
152
  @team.on_base << 'player1'
52
153
  @team.on_base << 'player2'
53
- puts @team.on_base # ['player1', 'player2']
154
+ @team.on_base << 'player3'
155
+ @team.on_base # ['player1', 'player2']
54
156
  @team.on_base.pop
157
+ @team.on_base.shift
158
+ @team.on_base.length # 1
159
+ @team.on_base.delete('player3')
55
160
 
56
- With one purposeful exception - counters cannot be set, only incremented/decremented:
57
-
58
- @team.drafted_players.increment # or incr
59
- @team.drafted_players.decrement # or decr
60
- @team.drafted_players = 4 # exception
61
- @team.drafted_players += 1 # exception
161
+ Sets work like the Ruby {Set}[http://ruby-doc.org/core/classes/Set.html] class:
162
+
163
+ @team.outfielders << 'outfielder1' << 'outfielder1'
164
+ @team.outfielders << 'outfielder2'
165
+ @team.outfielders # ['outfielder1', 'outfielder2']
166
+ @team.outfielders.each do |player|
167
+ puts player
168
+ end
169
+ player = @team.outfielders.detect{|of| of == 'outfielder2'}
62
170
 
63
- === Instance Usage
171
+ Note counters cannot be assigned to, only incremented/decremented:
64
172
 
65
- === Counters
173
+ @team.hits.increment # or incr
174
+ @team.hits.decrement # or decr
175
+ @team.hits.incr(3) # add 3
176
+ @team.runs = 4 # exception
66
177
 
67
- @counter = Redis::Counter.new('counter_name')
68
- @counter.increment
69
- puts @counter
70
- puts @counter.get # force re-fetch
178
+ For free, you get a +redis+ handle usable in your class:
71
179
 
72
- === Lists
180
+ @team.redis.get('somekey')
181
+ @team.redis.smembers('someset')
73
182
 
74
- @list = Redis::List.new('list_name')
75
- @list << 'a'
76
- @list << 'b'
77
- puts @list
183
+ You can call any operation supported by {Redis}[http://code.google.com/p/redis/wiki/CommandReference]
78
184
 
79
185
  == Atomicity
80
186
 
81
187
  You are probably not handling atomicity correctly in your app. For a fun rant
82
- on the topic, see {ATOMICITY}[ATOMICITY.doc]
188
+ on the topic, see
189
+ {ATOMICITY}[http://github.com/nateware/redis-objects/blob/master/ATOMICITY.rdoc].
83
190
 
84
191
  Atomic counters are a good way to handle concurrency:
85
192
 
data/lib/redis/counter.rb CHANGED
@@ -8,12 +8,11 @@ class Redis
8
8
  #
9
9
  class Counter
10
10
  attr_reader :key, :options, :redis
11
- def initialize(key, options={})
11
+ def initialize(key, redis=$redis, options={})
12
12
  @key = key
13
+ @redis = redis
13
14
  @options = options
14
- @redis = options[:redis] || $redis || Redis::Objects.redis
15
15
  @options[:start] ||= 0
16
- @options[:type] ||= @options[:start] == 0 ? :increment : :decrement
17
16
  @redis.setnx(key, @options[:start]) unless @options[:start] == 0 || @options[:init] === false
18
17
  end
19
18
 
data/lib/redis/list.rb CHANGED
@@ -4,92 +4,119 @@ class Redis
4
4
  # behave as much like Ruby arrays as possible.
5
5
  #
6
6
  class List
7
+ require 'enumerator'
8
+ include Enumerable
9
+
7
10
  attr_reader :key, :options, :redis
8
- def initialize(key, options={})
11
+ def initialize(key, redis=$redis, options={})
9
12
  @key = key
13
+ @redis = redis
10
14
  @options = options
11
- @redis = options[:redis] || $redis || Redis::Objects.redis
12
- @options[:start] ||= 0
13
- @options[:type] ||= @options[:start] == 0 ? :increment : :decrement
14
- @redis.setnx(key, @options[:start]) unless @options[:start] == 0 || @options[:init] === false
15
15
  end
16
-
16
+
17
+ # Works like push. Can chain together: list << 'a' << 'b'
17
18
  def <<(value)
18
19
  push(value)
20
+ self # for << 'a' << 'b'
19
21
  end
20
-
22
+
23
+ # Add a member to the end of the list. Redis: RPUSH
21
24
  def push(value)
22
25
  redis.rpush(key, value)
23
- @values << value
24
26
  end
25
27
 
28
+ # Remove a member from the end of the list. Redis: RPOP
26
29
  def pop
27
30
  redis.rpop(key)
28
- @values.pop
29
31
  end
30
32
 
33
+ # Add a member to the start of the list. Redis: LPUSH
31
34
  def unshift(value)
32
35
  redis.lpush(key, value)
33
- @values.unshift value
34
36
  end
35
37
 
38
+ # Remove a member from the start of the list. Redis: LPOP
36
39
  def shift
37
40
  redis.lpop(key)
38
41
  end
39
42
 
43
+ # Return all values in the list. Redis: LRANGE(0,-1)
40
44
  def values
41
- @values ||= get
45
+ range(0, -1)
42
46
  end
47
+ alias_method :get, :values
43
48
 
44
- def value=(val)
45
- redis.set(key, val)
46
- @values = val
47
- end
48
-
49
- def get
50
- @values = range(0, -1)
51
- end
52
-
53
- def [](index)
54
- case index
55
- when Range
49
+ # Same functionality as Ruby arrays. If a single number is given, return
50
+ # just the element at that index using Redis: LINDEX. Otherwise, return
51
+ # a range of values using Redis: LRANGE.
52
+ def [](index, length=nil)
53
+ if index.is_a? Range
56
54
  range(index.first, index.last)
55
+ elsif length
56
+ range(index, length)
57
57
  else
58
- range(index, index)
58
+ at(index)
59
59
  end
60
60
  end
61
61
 
62
+ # Delete the element(s) from the list that match name. If count is specified,
63
+ # only the first-N (if positive) or last-N (if negative) will be removed.
64
+ # Redis: LREM
62
65
  def delete(name, count=0)
63
- redis.lrem(name, count)
66
+ redis.lrem(key, count, name) # weird api
67
+ get
68
+ end
69
+
70
+ # Iterate through each member of the set. Redis::Objects mixes in Enumerable,
71
+ # so you can also use familiar methods like +collect+, +detect+, and so forth.
72
+ def each(&block)
73
+ values.each(&block)
64
74
  end
65
75
 
76
+ # Return a range of values from +start_index+ to +end_index+. Can also use
77
+ # the familiar list[start,end] Ruby syntax. Redis: LRANGE
66
78
  def range(start_index, end_index)
67
79
  redis.lrange(key, start_index, end_index)
68
80
  end
69
-
81
+
82
+ # Return the value at the given index. Can also use familiar list[index] syntax.
83
+ # Redis: LINDEX
70
84
  def at(index)
71
- redis.lrange(key, index, index)
85
+ redis.lindex(key, index)
86
+ end
87
+
88
+ # Return the first element in the list. Redis: LINDEX(0)
89
+ def first
90
+ at(0)
72
91
  end
73
92
 
93
+ # Return the last element in the list. Redis: LINDEX(-1)
74
94
  def last
75
- redis.lrange(key, -1, -1)
95
+ at(-1)
76
96
  end
77
97
 
98
+ # Clear the list entirely. Redis: DEL
78
99
  def clear
79
100
  redis.del(key)
80
101
  end
81
102
 
103
+ # Return the length of the list. Aliased as size. Redis: LLEN
82
104
  def length
83
- redis.length
105
+ redis.llen(key)
84
106
  end
85
107
  alias_method :size, :length
86
-
108
+
109
+ # Returns true if there are no elements in the list. Redis: LLEN == 0
87
110
  def empty?
88
- values.empty?
111
+ length == 0
89
112
  end
90
-
113
+
91
114
  def ==(x)
92
115
  values == x
93
116
  end
117
+
118
+ def to_s
119
+ values.join(', ')
120
+ end
94
121
  end
95
122
  end
data/lib/redis/lock.rb CHANGED
@@ -10,11 +10,11 @@ class Redis
10
10
  class LockTimeout < StandardError; end #:nodoc:
11
11
 
12
12
  attr_reader :key, :options, :redis
13
- def initialize(key, options={})
13
+ def initialize(key, redis=$redis, options={})
14
14
  @key = key
15
+ @redis = redis
15
16
  @options = options
16
17
  @options[:timeout] ||= 5
17
- @redis = options[:redis] || $redis || Redis::Objects.redis
18
18
  @redis.setnx(key, @options[:start]) unless @options[:start] == 0 || @options[:init] === false
19
19
  end
20
20
 
@@ -24,7 +24,7 @@ class Redis
24
24
  @counters[name] = options
25
25
  class_eval <<-EndMethods
26
26
  def #{name}
27
- @#{name} ||= Redis::Counter.new(field_key(:#{name}), self.class.counters[:#{name}].merge(:redis => redis))
27
+ @#{name} ||= Redis::Counter.new(field_key(:#{name}), redis, self.class.counters[:#{name}])
28
28
  end
29
29
  EndMethods
30
30
  end
@@ -20,7 +20,7 @@ class Redis
20
20
  @lists[name] = options
21
21
  class_eval <<-EndMethods
22
22
  def #{name}
23
- @#{name} ||= Redis::List.new(field_key(:#{name}), self.class.lists[:#{name}].merge(:redis => redis))
23
+ @#{name} ||= Redis::List.new(field_key(:#{name}), redis, self.class.lists[:#{name}])
24
24
  end
25
25
  EndMethods
26
26
  end
@@ -22,7 +22,7 @@ class Redis
22
22
  @locks[name] = options
23
23
  class_eval <<-EndMethods
24
24
  def #{name}_lock(&block)
25
- @#{name}_lock ||= Redis::Lock.new(field_key(:#{name}_lock), self.class.locks[:#{name}].merge(:redis => redis))
25
+ @#{name}_lock ||= Redis::Lock.new(field_key(:#{name}_lock), redis, self.class.locks[:#{name}])
26
26
  end
27
27
  EndMethods
28
28
  end
@@ -4,6 +4,31 @@ require 'redis/set'
4
4
  class Redis
5
5
  module Objects
6
6
  module Sets
7
+ def self.included(klass)
8
+ klass.instance_variable_set('@sets', {})
9
+ klass.send :include, InstanceMethods
10
+ klass.extend ClassMethods
11
+ end
12
+
13
+ # Class methods that appear in your class when you include Redis::Objects.
14
+ module ClassMethods
15
+ attr_reader :sets
16
+
17
+ # Define a new list. It will function like a regular instance
18
+ # method, so it can be used alongside ActiveRecord, DataMapper, etc.
19
+ def set(name, options={})
20
+ @sets[name] = options
21
+ class_eval <<-EndMethods
22
+ def #{name}
23
+ @#{name} ||= Redis::Set.new(field_key(:#{name}), redis, self.class.sets[:#{name}])
24
+ end
25
+ EndMethods
26
+ end
27
+ end
28
+
29
+ # Instance methods that appear in your class when you include Redis::Objects.
30
+ module InstanceMethods
31
+ end
7
32
  end
8
33
  end
9
34
  end
@@ -20,7 +20,7 @@ class Redis
20
20
  @values[name] = options
21
21
  class_eval <<-EndMethods
22
22
  def #{name}
23
- @#{name} ||= Redis::Value.new(field_key(:#{name}), self.class.values[:#{name}].merge(:redis => redis))
23
+ @#{name} ||= Redis::Value.new(field_key(:#{name}), redis, self.class.values[:#{name}])
24
24
  end
25
25
  def #{name}=(value)
26
26
  #{name}.value = value
data/lib/redis/set.rb CHANGED
@@ -3,5 +3,131 @@ class Redis
3
3
  # Class representing a set.
4
4
  #
5
5
  class Set
6
+ require 'enumerator'
7
+ include Enumerable
8
+
9
+ attr_reader :key, :options, :redis
10
+
11
+ # Create a new Set.
12
+ def initialize(key, redis=$redis, options={})
13
+ @key = key
14
+ @redis = redis
15
+ @options = options
16
+ end
17
+
18
+ # Works like add. Can chain together: list << 'a' << 'b'
19
+ def <<(value)
20
+ add(value)
21
+ self # for << 'a' << 'b'
22
+ end
23
+
24
+ # Add the specified value to the set only if it does not exist already.
25
+ # Redis: SADD
26
+ def add(value)
27
+ redis.sadd(key, value)
28
+ end
29
+
30
+ # Return all members in the set. Redis: SMEMBERS
31
+ def members
32
+ redis.smembers(key)
33
+ end
34
+ alias_method :get, :members
35
+
36
+ # Returns true if the specified value is in the set. Redis: SISMEMBER
37
+ def member?(value)
38
+ redis.sismember(key, value)
39
+ end
40
+ alias_method :include?, :member?
41
+
42
+ # Delete the value from the set. Redis: SREM
43
+ def delete(name)
44
+ redis.srem(key, name)
45
+ get
46
+ end
47
+
48
+ # Wipe the set entirely. Redis: DEL
49
+ def clear
50
+ redis.del(key)
51
+ end
52
+
53
+ # Iterate through each member of the set. Redis::Objects mixes in Enumerable,
54
+ # so you can also use familiar methods like +collect+, +detect+, and so forth.
55
+ def each(&block)
56
+ members.each(&block)
57
+ end
58
+
59
+ # Return the intersection with another set. Can pass it either another set
60
+ # object or set name. Also available as & which is a bit cleaner:
61
+ #
62
+ # members_in_both = set1 & set2
63
+ #
64
+ # If you want to specify multiple sets, you must use +intersection+:
65
+ #
66
+ # members_in_all = set1.intersection(set2, set3, set4)
67
+ # members_in_all = set1.inter(set2, set3, set4) # alias
68
+ #
69
+ # Redis: SINTER
70
+ def intersection(*sets)
71
+ redis.sinter(key, *keys_from_objects(sets))
72
+ end
73
+ alias_method :intersect, :intersection
74
+ alias_method :inter, :intersection
75
+ alias_method :&, :intersection
76
+
77
+ # Calculate the intersection and store it in Redis as +name+. Returns the number
78
+ # of elements in the stored intersection. Redis: SUNIONSTORE
79
+ def interstore(name, *sets)
80
+ redis.sinterstore(name, key, *keys_from_objects(sets))
81
+ end
82
+
83
+ # Return the union with another set. Can pass it either another set
84
+ # object or set name. Also available as | and + which are a bit cleaner:
85
+ #
86
+ # members_in_either = set1 | set2
87
+ # members_in_either = set1 + set2
88
+ #
89
+ # If you want to specify multiple sets, you must use +union+:
90
+ #
91
+ # members_in_all = set1.union(set2, set3, set4)
92
+ #
93
+ # Redis: SUNION
94
+ def union(*sets)
95
+ redis.sunion(key, *keys_from_objects(sets))
96
+ end
97
+ alias_method :|, :union
98
+ alias_method :+, :union
99
+
100
+ # Calculate the union and store it in Redis as +name+. Returns the number
101
+ # of elements in the stored union. Redis: SINTERSTORE
102
+ def unionstore(name, *sets)
103
+ redis.sunionstore(name, key, *keys_from_objects(sets))
104
+ end
105
+
106
+ # The number of members in the set. Aliased as size. Redis: SCARD
107
+ def length
108
+ redis.scard(key)
109
+ end
110
+ alias_method :size, :length
111
+
112
+ # Returns true if the set has no members. Redis: SCARD == 0
113
+ def empty?
114
+ length == 0
115
+ end
116
+
117
+ def ==(x)
118
+ members == x
119
+ end
120
+
121
+ def to_s
122
+ members.join(', ')
123
+ end
124
+
125
+ private
126
+
127
+ def keys_from_objects(sets)
128
+ raise ArgumentError, "Must pass in one or more set names" if sets.empty?
129
+ sets.collect{|set| set.is_a?(Redis::Set) ? set.key : set}
130
+ end
131
+
6
132
  end
7
133
  end
data/lib/redis/value.rb CHANGED
@@ -4,10 +4,10 @@ class Redis
4
4
  #
5
5
  class Value
6
6
  attr_reader :key, :options, :redis
7
- def initialize(key, options={})
7
+ def initialize(key, redis=$redis, options={})
8
8
  @key = key
9
+ @redis = redis
9
10
  @options = options
10
- @redis = options[:redis] || $redis || Redis::Objects.redis
11
11
  @redis.setnx(key, @options[:default]) if @options[:default]
12
12
  end
13
13
 
@@ -0,0 +1,18 @@
1
+
2
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
3
+
4
+ describe Redis::List do
5
+
6
+
7
+ end
8
+
9
+ describe Redis::Set do
10
+
11
+
12
+ end
13
+
14
+ describe Redis::Value do
15
+
16
+
17
+ end
18
+
@@ -1,6 +1,9 @@
1
1
 
2
2
  require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
3
3
 
4
+ UNIONSTORE_KEY = 'test:unionstore'
5
+ INTERSTORE_KEY = 'test:interstore'
6
+
4
7
  class Roster
5
8
  include Redis::Objects
6
9
  counter :available_slots, :start => 10
@@ -10,6 +13,7 @@ class Roster
10
13
  lock :resort, :timeout => 2
11
14
  value :starting_pitcher
12
15
  list :player_stats
16
+ set :outfielders
13
17
 
14
18
  def initialize(id=1) @id = id end
15
19
  def id; @id; end
@@ -20,6 +24,10 @@ describe Redis::Objects do
20
24
  before :all do
21
25
  @roster = Roster.new
22
26
  @roster2 = Roster.new
27
+
28
+ @roster_1 = Roster.new(1)
29
+ @roster_2 = Roster.new(2)
30
+ @roster_3 = Roster.new(3)
23
31
  end
24
32
 
25
33
  before :each do
@@ -29,6 +37,12 @@ describe Redis::Objects do
29
37
  @roster.resort_lock.clear
30
38
  @roster.starting_pitcher.delete
31
39
  @roster.player_stats.clear
40
+ @roster.outfielders.clear
41
+ @roster_1.outfielders.clear
42
+ @roster_2.outfielders.clear
43
+ @roster_3.outfielders.clear
44
+ @roster.redis.del(UNIONSTORE_KEY)
45
+ @roster.redis.del(INTERSTORE_KEY)
32
46
  end
33
47
 
34
48
  it "should provide a connection method" do
@@ -254,6 +268,137 @@ describe Redis::Objects do
254
268
  @roster.player_stats.should be_empty
255
269
  @roster.player_stats << 'a'
256
270
  @roster.player_stats.should == ['a']
271
+ @roster.player_stats.get.should == ['a']
272
+ @roster.player_stats.unshift 'b'
273
+ @roster.player_stats.to_s.should == 'b, a'
274
+ @roster.player_stats.should == ['b','a']
275
+ @roster.player_stats.get.should == ['b','a']
276
+ @roster.player_stats.push 'c'
277
+ @roster.player_stats.should == ['b','a','c']
278
+ @roster.player_stats.get.should == ['b','a','c']
279
+ @roster.player_stats.first.should == 'b'
280
+ @roster.player_stats.last.should == 'c'
281
+ @roster.player_stats << 'd'
282
+ @roster.player_stats.should == ['b','a','c','d']
283
+ @roster.player_stats[1].should == 'a'
284
+ @roster.player_stats[0].should == 'b'
285
+ @roster.player_stats[2].should == 'c'
286
+ @roster.player_stats[3].should == 'd'
287
+ @roster.player_stats.include?('c').should be_true
288
+ @roster.player_stats.include?('no').should be_false
289
+ @roster.player_stats.pop
290
+ @roster.player_stats[0].should == @roster.player_stats.at(0)
291
+ @roster.player_stats[1].should == @roster.player_stats.at(1)
292
+ @roster.player_stats[2].should == @roster.player_stats.at(2)
293
+ @roster.player_stats.should == ['b','a','c']
294
+ @roster.player_stats.get.should == ['b','a','c']
295
+ @roster.player_stats.shift
296
+ @roster.player_stats.should == ['a','c']
297
+ @roster.player_stats.get.should == ['a','c']
298
+ @roster.player_stats << 'e' << 'f' << 'e'
299
+ @roster.player_stats.should == ['a','c','e','f','e']
300
+ @roster.player_stats.get.should == ['a','c','e','f','e']
301
+ @roster.player_stats.delete('e')
302
+ @roster.player_stats.should == ['a','c','f']
303
+ @roster.player_stats.get.should == ['a','c','f']
304
+ @roster.player_stats << 'j'
305
+ @roster.player_stats.should == ['a','c','f','j']
306
+ @roster.player_stats[0..2].should == ['a','c','f']
307
+ @roster.player_stats[1, 3].should == ['c','f','j']
308
+ @roster.player_stats.length.should == 4
309
+ @roster.player_stats.size.should == 4
310
+ @roster.player_stats.should == ['a','c','f','j']
311
+ @roster.player_stats.get.should == ['a','c','f','j']
312
+
313
+ i = -1
314
+ @roster.player_stats.each do |st|
315
+ st.should == @roster.player_stats[i += 1]
316
+ end
317
+ @roster.player_stats.should == ['a','c','f','j']
318
+ @roster.player_stats.get.should == ['a','c','f','j']
319
+
320
+ @roster.player_stats.each_with_index do |st,i|
321
+ st.should == @roster.player_stats[i]
322
+ end
323
+ @roster.player_stats.should == ['a','c','f','j']
324
+ @roster.player_stats.get.should == ['a','c','f','j']
325
+
326
+ coll = @roster.player_stats.collect{|st| st}
327
+ coll.should == ['a','c','f','j']
328
+ @roster.player_stats.should == ['a','c','f','j']
329
+ @roster.player_stats.get.should == ['a','c','f','j']
330
+
331
+ @roster.player_stats << 'a'
332
+ coll = @roster.player_stats.select{|st| st == 'a'}
333
+ coll.should == ['a','a']
334
+ @roster.player_stats.should == ['a','c','f','j','a']
335
+ @roster.player_stats.get.should == ['a','c','f','j','a']
336
+ end
337
+
338
+ it "should handle sets of simple values" do
339
+ @roster.outfielders.should be_empty
340
+ @roster.outfielders << 'a' << 'a' << 'a'
341
+ @roster.outfielders.should == ['a']
342
+ @roster.outfielders.get.should == ['a']
343
+ @roster.outfielders << 'b' << 'b'
344
+ @roster.outfielders.to_s.should == 'a, b'
345
+ @roster.outfielders.should == ['a','b']
346
+ @roster.outfielders.members.should == ['a','b']
347
+ @roster.outfielders.get.should == ['a','b']
348
+ @roster.outfielders << 'c'
349
+ @roster.outfielders.sort.should == ['a','b','c']
350
+ @roster.outfielders.get.sort.should == ['a','b','c']
351
+ @roster.outfielders.delete('c')
352
+ @roster.outfielders.should == ['a','b']
353
+ @roster.outfielders.get.sort.should == ['a','b']
354
+ @roster.outfielders.length.should == 2
355
+ @roster.outfielders.size.should == 2
356
+
357
+ i = 0
358
+ @roster.outfielders.each do |st|
359
+ i += 1
360
+ end
361
+ i.should == @roster.outfielders.length
362
+
363
+ coll = @roster.outfielders.collect{|st| st}
364
+ coll.should == ['a','b']
365
+ @roster.outfielders.should == ['a','b']
366
+ @roster.outfielders.get.should == ['a','b']
367
+
368
+ @roster.outfielders << 'c'
369
+ @roster.outfielders.member?('c').should be_true
370
+ @roster.outfielders.include?('c').should be_true
371
+ @roster.outfielders.member?('no').should be_false
372
+ coll = @roster.outfielders.select{|st| st == 'c'}
373
+ coll.should == ['c']
374
+ @roster.outfielders.sort.should == ['a','b','c']
375
+ end
376
+
377
+ it "should handle set intersections and unions" do
378
+ @roster_1.outfielders << 'a' << 'b' << 'c' << 'd' << 'e'
379
+ @roster_2.outfielders << 'c' << 'd' << 'e' << 'f' << 'g'
380
+ @roster_3.outfielders << 'a' << 'd' << 'g' << 'l' << 'm'
381
+ @roster_1.outfielders.sort.should == %w(a b c d e)
382
+ @roster_2.outfielders.sort.should == %w(c d e f g)
383
+ @roster_3.outfielders.sort.should == %w(a d g l m)
384
+ (@roster_1.outfielders & @roster_2.outfielders).sort.should == ['c','d','e']
385
+ @roster_1.outfielders.intersection(@roster_2.outfielders).sort.should == ['c','d','e']
386
+ @roster_1.outfielders.intersection(@roster_2.outfielders, @roster_3.outfielders).sort.should == ['d']
387
+ @roster_1.outfielders.intersect(@roster_2.outfielders).sort.should == ['c','d','e']
388
+ @roster_1.outfielders.inter(@roster_2.outfielders, @roster_3.outfielders).sort.should == ['d']
389
+ @roster_1.outfielders.interstore(INTERSTORE_KEY, @roster_2.outfielders).should == 3
390
+ @roster_1.redis.smembers(INTERSTORE_KEY).sort.should == ['c','d','e']
391
+ @roster_1.outfielders.interstore(INTERSTORE_KEY, @roster_2.outfielders, @roster_3.outfielders).should == 1
392
+ @roster_1.redis.smembers(INTERSTORE_KEY).sort.should == ['d']
393
+
394
+ (@roster_1.outfielders | @roster_2.outfielders).sort.should == ['a','b','c','d','e','f','g']
395
+ (@roster_1.outfielders + @roster_2.outfielders).sort.should == ['a','b','c','d','e','f','g']
396
+ @roster_1.outfielders.union(@roster_2.outfielders).sort.should == ['a','b','c','d','e','f','g']
397
+ @roster_1.outfielders.union(@roster_2.outfielders, @roster_3.outfielders).sort.should == ['a','b','c','d','e','f','g','l','m']
398
+ @roster_1.outfielders.unionstore(UNIONSTORE_KEY, @roster_2.outfielders).should == 7
399
+ @roster_1.redis.smembers(UNIONSTORE_KEY).sort.should == ['a','b','c','d','e','f','g']
400
+ @roster_1.outfielders.unionstore(UNIONSTORE_KEY, @roster_2.outfielders, @roster_3.outfielders).should == 9
401
+ @roster_1.redis.smembers(UNIONSTORE_KEY).sort.should == ['a','b','c','d','e','f','g','l','m']
257
402
  end
258
403
 
259
404
  it "should provide a lock method that accepts a block" do
@@ -265,7 +410,7 @@ describe Redis::Objects do
265
410
  a.should be_true
266
411
  end
267
412
 
268
- xit "should raise an exception if the timeout is exceeded" do
413
+ it "should raise an exception if the timeout is exceeded" do
269
414
  @roster.redis.set(@roster.resort_lock.key, 1)
270
415
  error = nil
271
416
  begin
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.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nate Wiger
@@ -9,11 +9,20 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-11-24 00:00:00 -08:00
12
+ date: 2009-11-26 00:00:00 -08:00
13
13
  default_executable:
14
- dependencies: []
15
-
16
- description: Redis::Objects is a lightweight library that lets you use Redis primitives as Ruby objects from any class or ORM
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: redis
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0.1"
24
+ version:
25
+ description: Map Redis types directly to Ruby objects. Works with any class or ORM.
17
26
  email: nate@wiger.org
18
27
  executables: []
19
28
 
@@ -36,6 +45,7 @@ files:
36
45
  - lib/redis/objects.rb
37
46
  - lib/redis/set.rb
38
47
  - lib/redis/value.rb
48
+ - spec/redis_objects_instance_spec.rb
39
49
  - spec/redis_objects_model_spec.rb
40
50
  - spec/spec_helper.rb
41
51
  - ATOMICITY.rdoc
@@ -62,12 +72,12 @@ required_rubygems_version: !ruby/object:Gem::Requirement
62
72
  - !ruby/object:Gem::Version
63
73
  version: "0"
64
74
  version:
65
- requirements: []
66
-
75
+ requirements:
76
+ - redis, v0.1 or greater
67
77
  rubyforge_project: redis-objects
68
78
  rubygems_version: 1.3.5
69
79
  signing_key:
70
80
  specification_version: 3
71
- summary: Lightweight map of Redis types to Ruby objects
81
+ summary: Maps Redis types to Ruby objects
72
82
  test_files: []
73
83