redis-objects 0.2.1 → 0.2.4

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,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,64 +15,81 @@ 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 :counters, :initialized_counters
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
- @counters[name] = options
25
- class_eval <<-EndMethods
26
- def #{name}
27
- @#{name} ||= Redis::Counter.new(field_key(:#{name}), redis, self.class.counters[:#{name}])
28
- end
29
- EndMethods
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
- value = redis.incr(field_key(name, id), by).to_i
59
+ value = redis.incrby(field_key(name, id), by).to_i
46
60
  block_given? ? rewindable_block(:decrement_counter, name, id, value, &block) : value
47
61
  end
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
- value = redis.decr(field_key(name, id), by).to_i
68
+ value = redis.decrby(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 = @counters[name][:start] if to.nil?
62
- redis.set(field_key(name, id), 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?
76
+ redis.set(field_key(name, id), to.to_i)
77
+ true
63
78
  end
64
79
 
65
80
  private
66
81
 
67
- def verify_counter_defined!(name) #:nodoc:
68
- raise Redis::Objects::UndefinedCounter, "Undefined counter :#{name} for class #{self.name}" unless @counters.has_key?(name)
82
+ def verify_counter_defined!(name, id) #:nodoc:
83
+ raise Redis::Objects::UndefinedCounter, "Undefined counter :#{name} for class #{self.name}" unless @redis_objects.has_key?(name)
84
+ if id.nil? and !@redis_objects[name][:global]
85
+ raise Redis::Objects::MissingID, "Missing ID for non-global counter #{self.name}##{name}"
86
+ end
69
87
  end
70
88
 
71
89
  def initialize_counter!(name, id) #:nodoc:
72
90
  key = field_key(name, id)
73
91
  unless @initialized_counters[key]
74
- redis.setnx(key, @counters[name][:start])
92
+ redis.setnx(key, @redis_objects[name][:start])
75
93
  end
76
94
  @initialized_counters[key] = true
77
95
  end
@@ -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
- @lists[name] = options
21
- class_eval <<-EndMethods
22
- def #{name}
23
- @#{name} ||= Redis::List.new(field_key(:#{name}), redis, self.class.lists[:#{name}])
24
- end
25
- EndMethods
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
 
@@ -6,25 +6,35 @@ 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
- @locks[name] = options
23
- class_eval <<-EndMethods
24
- def #{name}_lock(&block)
25
- @#{name}_lock ||= Redis::Lock.new(field_key(:#{name}_lock), redis, self.class.locks[:#{name}])
26
- end
27
- EndMethods
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
28
38
  end
29
39
 
30
40
  # Obtain a lock, and execute the block synchronously. Any other code
@@ -34,7 +44,7 @@ class Redis
34
44
  verify_lock_defined!(name)
35
45
  raise ArgumentError, "Missing block to #{self.name}.obtain_lock" unless block_given?
36
46
  lock_name = field_key("#{name}_lock", id)
37
- Redis::Lock.new(redis, lock_name, self.class.locks[name]).lock(&block)
47
+ Redis::Lock.new(lock_name, redis, self.redis_objects[name]).lock(&block)
38
48
  end
39
49
 
40
50
  # Clear the lock. Use with care - usually only in an Admin page to clear
@@ -48,7 +58,7 @@ class Redis
48
58
  private
49
59
 
50
60
  def verify_lock_defined!(name)
51
- raise Redis::Objects::UndefinedLock, "Undefined lock :#{name} for class #{self.name}" unless @locks.has_key?(name)
61
+ raise Redis::Objects::UndefinedLock, "Undefined lock :#{name} for class #{self.name}" unless @redis_objects.has_key?(name)
52
62
  end
53
63
  end
54
64
  end
@@ -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
- @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
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
 
@@ -0,0 +1,45 @@
1
+ # This is the class loader, for use as "include Redis::Objects::Sets"
2
+ # For the object itself, see "Redis::Set"
3
+ require 'redis/sorted_set'
4
+ class Redis
5
+ module Objects
6
+ module SortedSets
7
+ def self.included(klass)
8
+ klass.send :include, InstanceMethods
9
+ klass.extend ClassMethods
10
+ end
11
+
12
+ # Class methods that appear in your class when you include Redis::Objects.
13
+ module ClassMethods
14
+ # Define a new list. It will function like a regular instance
15
+ # method, so it can be used alongside ActiveRecord, DataMapper, etc.
16
+ def sorted_set(name, options={})
17
+ @redis_objects[name] = options.merge(:type => :sorted_set)
18
+ if options[:global]
19
+ instance_eval <<-EndMethods
20
+ def #{name}
21
+ @#{name} ||= Redis::SortedSet.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::SortedSet.new(field_key(:#{name}), redis, self.class.redis_objects[:#{name}])
33
+ end
34
+ EndMethods
35
+ end
36
+
37
+ end
38
+ end
39
+
40
+ # Instance methods that appear in your class when you include Redis::Objects.
41
+ module InstanceMethods
42
+ end
43
+ end
44
+ end
45
+ end
@@ -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
- @values[name] = options
21
- class_eval <<-EndMethods
22
- def #{name}
23
- @#{name} ||= Redis::Value.new(field_key(:#{name}), redis, self.class.values[:#{name}])
24
- end
25
- def #{name}=(value)
26
- #{name}.value = value
27
- end
28
- EndMethods
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
@@ -6,7 +6,7 @@ class Redis
6
6
  # Redis::Objects enables high-performance atomic operations in your app
7
7
  # by leveraging the atomic features of the Redis server. To use Redis::Objects,
8
8
  # first include it in any class you want. (This example uses an ActiveRecord
9
- # subclass, but that is *not* required.) Then, use +counter+ and +lock+
9
+ # subclass, but that is *not* required.) Then, use +counter+, +lock+, +set+, etc
10
10
  # to define your primitives:
11
11
  #
12
12
  # class Game < ActiveRecord::Base
@@ -14,8 +14,8 @@ class Redis
14
14
  #
15
15
  # counter :joined_players
16
16
  # counter :active_players
17
- # set :player_ids
18
17
  # lock :archive_game
18
+ # set :player_ids
19
19
  # end
20
20
  #
21
21
  # The, you can use these counters both for bookeeping and as atomic actions:
@@ -39,10 +39,11 @@ class Redis
39
39
  dir = File.expand_path(__FILE__.sub(/\.rb$/,''))
40
40
 
41
41
  autoload :Counters, File.join(dir, 'counters')
42
- autoload :Values, File.join(dir, 'values')
43
42
  autoload :Lists, File.join(dir, 'lists')
44
- autoload :Sets, File.join(dir, 'sets')
45
43
  autoload :Locks, File.join(dir, 'locks')
44
+ autoload :Sets, File.join(dir, 'sets')
45
+ autoload :SortedSets, File.join(dir, 'sorted_sets')
46
+ autoload :Values, File.join(dir, 'values')
46
47
 
47
48
  class NotConnected < StandardError; end
48
49
 
@@ -55,22 +56,23 @@ class Redis
55
56
  def included(klass)
56
57
  # Core (this file)
57
58
  klass.instance_variable_set('@redis', @redis)
59
+ klass.instance_variable_set('@redis_objects', {})
58
60
  klass.send :include, InstanceMethods
59
61
  klass.extend ClassMethods
60
62
 
61
63
  # Pull in each object type
62
64
  klass.send :include, Redis::Objects::Counters
63
- klass.send :include, Redis::Objects::Values
64
65
  klass.send :include, Redis::Objects::Lists
65
- klass.send :include, Redis::Objects::Sets
66
66
  klass.send :include, Redis::Objects::Locks
67
+ klass.send :include, Redis::Objects::Sets
68
+ klass.send :include, Redis::Objects::SortedSets
69
+ klass.send :include, Redis::Objects::Values
67
70
  end
68
71
  end
69
72
 
70
73
  # Class methods that appear in your class when you include Redis::Objects.
71
74
  module ClassMethods
72
- attr_accessor :redis
73
-
75
+ attr_accessor :redis, :redis_objects
74
76
 
75
77
  # Set the Redis prefix to use. Defaults to model_name
76
78
  def prefix=(prefix) @prefix = prefix end
data/lib/redis/set.rb CHANGED
@@ -5,16 +5,18 @@ class Redis
5
5
  class Set
6
6
  require 'enumerator'
7
7
  include Enumerable
8
- require 'redis/serialize'
9
- include Redis::Serialize
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
 
13
15
  # Create a new Set.
14
- def initialize(key, redis=$redis, options={})
16
+ def initialize(key, *args)
15
17
  @key = key
16
- @redis = redis
17
- @options = options
18
+ @options = args.last.is_a?(Hash) ? args.pop : {}
19
+ @redis = args.first || $redis
18
20
  end
19
21
 
20
22
  # Works like add. Can chain together: list << 'a' << 'b'
@@ -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: SINTERSTORE
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)