redis-objects 0.1.0 → 0.1.1

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.
@@ -4,13 +4,12 @@ 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
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
9
  nothing to do with an ORM or even a database. That's where this gem comes in.
10
10
 
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
11
+ This gem originally arose out of a need for high-concurrency atomic operations;
12
+ for a fun rant on the topic, see
14
13
  {ATOMICITY}[http://github.com/nateware/redis-objects/blob/master/ATOMICITY.rdoc],
15
14
  or scroll down to "Atomicity" in this README.
16
15
 
@@ -23,6 +22,8 @@ by using +new+ with the type of data structure you want to create.
23
22
  gem tumble
24
23
  gem install redis-objects
25
24
 
25
+ == Example 1: Class Usage
26
+
26
27
  === Initialization
27
28
 
28
29
  # If on Rails, config/initializers/redis.rb is a good place for this
@@ -30,42 +31,76 @@ by using +new+ with the type of data structure you want to create.
30
31
  require 'redis/objects'
31
32
  Redis::Objects.redis = Redis.new(:host => 127.0.0.1, :port => 6379)
32
33
 
33
- == Examples
34
+ === Model Class
34
35
 
35
- === Model Class Usage
36
+ Include in any type of class:
36
37
 
37
38
  class Team < ActiveRecord::Base
38
39
  include Redis::Objects
39
40
 
40
- counter :drafted_players
41
- counter :active_players
42
- counter :total_online_players, :global => true
41
+ counter :hits
42
+ counter :runs
43
+ counter :outs
44
+ counter :inning, :start => 1
43
45
  list :on_base
44
46
  set :outfielders
45
- value :coach
47
+ value :at_bat
46
48
  end
47
49
 
48
- Familiar Ruby operations Just Work:
50
+ Familiar Ruby array operations Just Work (TM):
49
51
 
50
52
  @team = Team.find(1)
51
53
  @team.on_base << 'player1'
52
54
  @team.on_base << 'player2'
53
- puts @team.on_base # ['player1', 'player2']
55
+ @team.on_base << 'player3'
56
+ puts @team.on_base # ['player1', 'player2']
54
57
  @team.on_base.pop
58
+ @team.on_base.shift
59
+ @team.on_base.length # 1
60
+ @team.on_base.delete('player3')
61
+
62
+ Sets work like the Ruby {Set}[http://ruby-doc.org/core/classes/Set.html] class:
63
+
64
+ @team.outfielders['1b'] = 'outfielder1'
65
+ @team.outfielders['lf'] = 'outfielder3'
66
+ @team.outfielders['lf'] = 'outfielder2'
67
+ @team.outfielders.keys
68
+ @team.outfielders.each do |position,player|
69
+ puts "#{player} is playing #{position}"
70
+ end
71
+ position = @team.outfielders.detect{|pos,of| of == 'outfielder3'}
72
+
73
+ Note counters cannot be assigned to, only incremented/decremented:
74
+
75
+ @team.hits.increment # or incr
76
+ @team.hits.decrement # or decr
77
+ @team.runs = 4 # exception
78
+ @team.runs += 1 # exception
55
79
 
56
- With one purposeful exception - counters cannot be set, only incremented/decremented:
80
+ It would be cool to get that last one working, but Ruby's implementation of +=
81
+ is problematic.
57
82
 
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
83
+ == Example 2: Instance Usage
62
84
 
63
- === Instance Usage
85
+ Each data type can be used independently.
86
+
87
+ === Initialization
88
+
89
+ Can follow the +$redis+ global variable pattern:
90
+
91
+ $redis = Redis.new(:host => 'localhost', :port => 6379)
92
+ @value = Redis::Value.new('myvalue')
93
+
94
+ Or can pass the handle into the new method:
95
+
96
+ redis = Redis.new(:host => 'localhost', :port => 6379)
97
+ @value = Redis::Value.new('myvalue', redis)
64
98
 
65
99
  === Counters
66
100
 
67
101
  @counter = Redis::Counter.new('counter_name')
68
102
  @counter.increment
103
+ @counter.decrement
69
104
  puts @counter
70
105
  puts @counter.get # force re-fetch
71
106
 
@@ -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
 
@@ -4,71 +4,77 @@ 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
62
  def delete(name, count=0)
63
- redis.lrem(name, count)
63
+ redis.lrem(key, count, name) # weird api
64
+ get
65
+ end
66
+
67
+ def each(&block)
68
+ values.each(&block)
64
69
  end
65
70
 
66
71
  def range(start_index, end_index)
67
72
  redis.lrange(key, start_index, end_index)
68
73
  end
69
-
74
+
75
+ # Return the value at the given index.
70
76
  def at(index)
71
- redis.lrange(key, index, index)
77
+ redis.lindex(key, index)
72
78
  end
73
79
 
74
80
  def last
@@ -80,16 +86,20 @@ class Redis
80
86
  end
81
87
 
82
88
  def length
83
- redis.length
89
+ redis.llen(key)
84
90
  end
85
91
  alias_method :size, :length
86
92
 
87
93
  def empty?
88
- values.empty?
94
+ length == 0
89
95
  end
90
-
96
+
91
97
  def ==(x)
92
98
  values == x
93
99
  end
100
+
101
+ def to_s
102
+ values.join(', ')
103
+ end
94
104
  end
95
105
  end
@@ -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
@@ -3,5 +3,73 @@ 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
+ def initialize(key, redis=$redis, options={})
11
+ @key = key
12
+ @redis = redis
13
+ @options = options
14
+ end
15
+
16
+ # Works like add. Can chain together: list << 'a' << 'b'
17
+ def <<(value)
18
+ add(value)
19
+ self # for << 'a' << 'b'
20
+ end
21
+
22
+ # Add the specified value to the set only if it does not exist already.
23
+ # Redis: SADD
24
+ def add(value)
25
+ redis.sadd(key, value)
26
+ end
27
+
28
+ # Return all members in the set. Redis: SMEMBERS
29
+ def members
30
+ redis.smembers(key)
31
+ end
32
+ alias_method :get, :members
33
+
34
+ # Returns true if the specified value is in the set. Redis: SISMEMBER
35
+ def member?(value)
36
+ redis.sismember(key, value)
37
+ end
38
+
39
+ # Delete the value from the set. Redis: SREM
40
+ def delete(name)
41
+ redis.srem(key, name)
42
+ get
43
+ end
44
+
45
+ # Wipe the set entirely. Redis: DEL
46
+ def clear
47
+ redis.del(key)
48
+ end
49
+
50
+ # Iterate through each member of the set. Redis::Objects mixes in Enumerable,
51
+ # so you can also use familiar methods like +collect+, +detect+, and so forth.
52
+ def each(&block)
53
+ members.each(&block)
54
+ end
55
+
56
+ # The number of members in the set. Aliased as size. Redis: SCARD
57
+ def length
58
+ redis.scard(key)
59
+ end
60
+ alias_method :size, :length
61
+
62
+ # Returns true if the set has no members. Redis: SCARD == 0
63
+ def empty?
64
+ length == 0
65
+ end
66
+
67
+ def ==(x)
68
+ members == x
69
+ end
70
+
71
+ def to_s
72
+ members.join(', ')
73
+ end
6
74
  end
7
75
  end
@@ -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
 
@@ -10,6 +10,7 @@ class Roster
10
10
  lock :resort, :timeout => 2
11
11
  value :starting_pitcher
12
12
  list :player_stats
13
+ set :outfielders
13
14
 
14
15
  def initialize(id=1) @id = id end
15
16
  def id; @id; end
@@ -29,6 +30,7 @@ describe Redis::Objects do
29
30
  @roster.resort_lock.clear
30
31
  @roster.starting_pitcher.delete
31
32
  @roster.player_stats.clear
33
+ @roster.outfielders.clear
32
34
  end
33
35
 
34
36
  it "should provide a connection method" do
@@ -254,6 +256,104 @@ describe Redis::Objects do
254
256
  @roster.player_stats.should be_empty
255
257
  @roster.player_stats << 'a'
256
258
  @roster.player_stats.should == ['a']
259
+ @roster.player_stats.get.should == ['a']
260
+ @roster.player_stats.unshift 'b'
261
+ @roster.player_stats.to_s.should == 'b, a'
262
+ @roster.player_stats.should == ['b','a']
263
+ @roster.player_stats.get.should == ['b','a']
264
+ @roster.player_stats.push 'c'
265
+ @roster.player_stats.should == ['b','a','c']
266
+ @roster.player_stats.get.should == ['b','a','c']
267
+ @roster.player_stats << 'd'
268
+ @roster.player_stats.should == ['b','a','c','d']
269
+ @roster.player_stats[1].should == 'a'
270
+ @roster.player_stats[0].should == 'b'
271
+ @roster.player_stats[2].should == 'c'
272
+ @roster.player_stats[3].should == 'd'
273
+ @roster.player_stats.pop
274
+ @roster.player_stats[0].should == @roster.player_stats.at(0)
275
+ @roster.player_stats[1].should == @roster.player_stats.at(1)
276
+ @roster.player_stats[2].should == @roster.player_stats.at(2)
277
+ @roster.player_stats.should == ['b','a','c']
278
+ @roster.player_stats.get.should == ['b','a','c']
279
+ @roster.player_stats.shift
280
+ @roster.player_stats.should == ['a','c']
281
+ @roster.player_stats.get.should == ['a','c']
282
+ @roster.player_stats << 'e' << 'f' << 'e'
283
+ @roster.player_stats.should == ['a','c','e','f','e']
284
+ @roster.player_stats.get.should == ['a','c','e','f','e']
285
+ @roster.player_stats.delete('e')
286
+ @roster.player_stats.should == ['a','c','f']
287
+ @roster.player_stats.get.should == ['a','c','f']
288
+ @roster.player_stats << 'j'
289
+ @roster.player_stats.should == ['a','c','f','j']
290
+ @roster.player_stats[0..2].should == ['a','c','f']
291
+ @roster.player_stats[1, 3].should == ['c','f','j']
292
+ @roster.player_stats.length.should == 4
293
+ @roster.player_stats.size.should == 4
294
+ @roster.player_stats.should == ['a','c','f','j']
295
+ @roster.player_stats.get.should == ['a','c','f','j']
296
+
297
+ i = -1
298
+ @roster.player_stats.each do |st|
299
+ st.should == @roster.player_stats[i += 1]
300
+ end
301
+ @roster.player_stats.should == ['a','c','f','j']
302
+ @roster.player_stats.get.should == ['a','c','f','j']
303
+
304
+ @roster.player_stats.each_with_index do |st,i|
305
+ st.should == @roster.player_stats[i]
306
+ end
307
+ @roster.player_stats.should == ['a','c','f','j']
308
+ @roster.player_stats.get.should == ['a','c','f','j']
309
+
310
+ coll = @roster.player_stats.collect{|st| st}
311
+ coll.should == ['a','c','f','j']
312
+ @roster.player_stats.should == ['a','c','f','j']
313
+ @roster.player_stats.get.should == ['a','c','f','j']
314
+
315
+ @roster.player_stats << 'a'
316
+ coll = @roster.player_stats.select{|st| st == 'a'}
317
+ coll.should == ['a','a']
318
+ @roster.player_stats.should == ['a','c','f','j','a']
319
+ @roster.player_stats.get.should == ['a','c','f','j','a']
320
+ end
321
+
322
+ it "should handle sets of simple values" do
323
+ @roster.outfielders.should be_empty
324
+ @roster.outfielders << 'a' << 'a' << 'a'
325
+ @roster.outfielders.should == ['a']
326
+ @roster.outfielders.get.should == ['a']
327
+ @roster.outfielders << 'b' << 'b'
328
+ @roster.outfielders.to_s.should == 'a, b'
329
+ @roster.outfielders.should == ['a','b']
330
+ @roster.outfielders.members.should == ['a','b']
331
+ @roster.outfielders.get.should == ['a','b']
332
+ @roster.outfielders << 'c'
333
+ @roster.outfielders.sort.should == ['a','b','c']
334
+ @roster.outfielders.get.sort.should == ['a','b','c']
335
+ @roster.outfielders.delete('c')
336
+ @roster.outfielders.should == ['a','b']
337
+ @roster.outfielders.get.sort.should == ['a','b']
338
+ @roster.outfielders.length.should == 2
339
+ @roster.outfielders.size.should == 2
340
+
341
+ i = 0
342
+ @roster.outfielders.each do |st|
343
+ i += 1
344
+ end
345
+ i.should == @roster.outfielders.length
346
+
347
+ coll = @roster.outfielders.collect{|st| st}
348
+ coll.should == ['a','b']
349
+ @roster.outfielders.should == ['a','b']
350
+ @roster.outfielders.get.should == ['a','b']
351
+
352
+ @roster.outfielders << 'c'
353
+ @roster.outfielders.member? 'c'
354
+ coll = @roster.outfielders.select{|st| st == 'c'}
355
+ coll.should == ['c']
356
+ @roster.outfielders.sort.should == ['a','b','c']
257
357
  end
258
358
 
259
359
  it "should provide a lock method that accepts a block" do
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.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nate Wiger
@@ -9,11 +9,11 @@ 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
14
  dependencies: []
15
15
 
16
- description: Redis::Objects is a lightweight library that lets you use Redis primitives as Ruby objects from any class or ORM
16
+ description: Map Redis types directly to Ruby objects. Works with any class or ORM.
17
17
  email: nate@wiger.org
18
18
  executables: []
19
19
 
@@ -68,6 +68,6 @@ rubyforge_project: redis-objects
68
68
  rubygems_version: 1.3.5
69
69
  signing_key:
70
70
  specification_version: 3
71
- summary: Lightweight map of Redis types to Ruby objects
71
+ summary: Maps Redis types to Ruby objects
72
72
  test_files: []
73
73