redpear 0.6.4 → 0.7.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.
@@ -1,6 +1,9 @@
1
1
  module Redpear::Schema
2
- extend Redpear::Concern
3
2
  autoload :Collection, 'redpear/schema/collection'
3
+ autoload :Column, 'redpear/schema/column'
4
+ autoload :Index, 'redpear/schema/index'
5
+ autoload :Score, 'redpear/schema/score'
6
+ extend Redpear::Concern
4
7
 
5
8
  module ClassMethods
6
9
 
@@ -9,23 +12,23 @@ module Redpear::Schema
9
12
  @columns ||= Redpear::Schema::Collection.new
10
13
  end
11
14
 
12
- # @param [multiple] the column definition. Please see Redpear::Column#initialize
15
+ # @param [multiple] the column definition. Please see Redpear::Schema::Column#initialize
13
16
  def column(*args)
14
- columns.store(Redpear::Column, self, *args).tap do |col|
17
+ columns.store(Redpear::Schema::Column, self, *args).tap do |col|
15
18
  __define_attribute_accessors__(col)
16
19
  end
17
20
  end
18
21
 
19
22
  # @param [multiple] the index definition. Please see Redpear::Index#initialize
20
23
  def index(*args)
21
- columns.store(Redpear::Index, self, *args).tap do |col|
24
+ columns.store(Redpear::Schema::Index, self, *args).tap do |col|
22
25
  __define_attribute_accessors__(col)
23
26
  end
24
27
  end
25
28
 
26
29
  # @param [multiple] the sorted index definition. Please see Redpear::ZIndex#initialize
27
- def zindex(*args)
28
- columns.store(Redpear::ZIndex, self, *args).tap do |col|
30
+ def score(*args)
31
+ columns.store(Redpear::Schema::Score, self, *args).tap do |col|
29
32
  __define_attribute_accessors__(col)
30
33
  end
31
34
  end
@@ -0,0 +1,120 @@
1
+ require 'securerandom'
2
+
3
+ class Redpear::Store::Base
4
+
5
+ attr_reader :key, :conn
6
+
7
+ # Creates and yields over a temporary key.
8
+ # Useful in combination with e.g. `interstore`, `unionstore`, etc.
9
+ #
10
+ # @param [Redpear::Connection] conn
11
+ # The connection
12
+ # @param [Hash] options
13
+ # The options hash
14
+ # @option [String] prefix
15
+ # Specify a key prefix. Example:
16
+ # Base.temporary conn, :prefix => "temp:" do |c|
17
+ # store.key # => temp:55ee0c1ec9530cf545bc25040beb4f292fd448af
18
+ # end
19
+ # @yield [Redpear::Store::Base]
20
+ # The temporary key
21
+ def self.temporary(conn, options = {})
22
+ store = nil
23
+ while !store || store.exists?
24
+ key = "#{options[:prefix]}#{SecureRandom.hex(20)}"
25
+ store = new(key, conn)
26
+ end
27
+ yield store
28
+ ensure
29
+ store.clear if store
30
+ end
31
+
32
+ # Constructor
33
+ # @param [String] key
34
+ # The storage key
35
+ # @param [Redpear::Connection] conn
36
+ # The connection
37
+ def initialize(key, conn)
38
+ @key, @conn = key, conn
39
+ end
40
+
41
+ alias to_s key
42
+
43
+ # @return [String] custom inspect
44
+ def inspect
45
+ "#<#{self.class.name} #{key}: #{value.inspect}>"
46
+ end
47
+
48
+ # @return [Boolean] true if the record exists
49
+ def exists?
50
+ !!conn.exists(key)
51
+ end
52
+
53
+ # @return [Integer] remaining time-to-live in seconds (if set)
54
+ def ttl
55
+ value = conn.ttl(key).to_i
56
+ value if value > -1
57
+ end
58
+
59
+ # @return [String] type information for this record
60
+ def type
61
+ conn.type(key).to_sym
62
+ end
63
+
64
+ # Expires the record
65
+ # @overload expire(time)
66
+ # @param [Time] time The time to expire the record at
67
+ # @overload expire(seconds)
68
+ # @param [Integer] seconds Expire in `seconds` from now
69
+ def expire(expiration)
70
+ case expiration
71
+ when Time
72
+ expire_at(expiration)
73
+ when Integer
74
+ expire_in(expiration)
75
+ when String
76
+ expiration = Kernel::Integer(expiration) rescue nil
77
+ expire(expiration)
78
+ else
79
+ false
80
+ end
81
+ end
82
+
83
+ # Deletes the whole record
84
+ def purge!
85
+ conn.del(key) == 1
86
+ end
87
+
88
+ # Deletes the record and returns the value
89
+ def clear
90
+ purge!
91
+ value
92
+ end
93
+
94
+ # Expires the record
95
+ # @param [Time] time The time to expire the record at
96
+ def expire_at(time)
97
+ conn.expireat key, time.to_i
98
+ end
99
+
100
+ # Expires the record
101
+ # @param [Integer] seconds Expire in `seconds` from now
102
+ def expire_in(seconds)
103
+ conn.expire key, seconds.to_i
104
+ end
105
+
106
+ # @abstract, override in subclasses
107
+ def value
108
+ nil
109
+ end
110
+
111
+ private
112
+
113
+ def range_pair(range)
114
+ first = range.first.to_i
115
+ last = range.last.to_i
116
+ last -= 1 if range.exclude_end?
117
+ [first, last]
118
+ end
119
+
120
+ end
@@ -0,0 +1,44 @@
1
+ class Redpear::Store::Counter < Redpear::Store::Value
2
+
3
+ # @return [Integer] the value
4
+ def get
5
+ super.to_i
6
+ end
7
+
8
+ # Sets the value
9
+ # @param [Integer] the value to set
10
+ def set(value)
11
+ super Kernel::Integer(value)
12
+ end
13
+
14
+ undef_method :append
15
+ undef_method :<<
16
+
17
+ # Increments the value
18
+ # @param [Integer] by
19
+ # The increment, defaults to 1
20
+ def increment(by = 1)
21
+ case by
22
+ when 1
23
+ conn.incr(key)
24
+ else
25
+ conn.incrby(key, by)
26
+ end
27
+ end
28
+ alias_method :next, :increment
29
+
30
+ # Decrements the value
31
+ # @param [Integer] by
32
+ # The decrement, defaults to 1
33
+ def decrement(by = 1)
34
+ case by
35
+ when 1
36
+ conn.decr(key)
37
+ else
38
+ conn.decrby(key, by)
39
+ end
40
+ end
41
+ alias_method :previous, :decrement
42
+ alias_method :prev, :decrement
43
+
44
+ end
@@ -0,0 +1,8 @@
1
+ class Redpear::Store::Enumerable < Redpear::Store::Base
2
+ include ::Enumerable
3
+
4
+ # Returns the array as the record's value
5
+ # @see Redpear::Store::Base#value
6
+ alias_method :value, :to_a
7
+
8
+ end
@@ -0,0 +1,120 @@
1
+ class Redpear::Store::Hash < Redpear::Store::Enumerable
2
+
3
+ # @yield over a field-value pair
4
+ # @yieldparam [String] field
5
+ # @yieldparam [String] value
6
+ def each(&block)
7
+ all.each(&block)
8
+ end
9
+
10
+ # @return [Hash] all pairs
11
+ def all
12
+ conn.hgetall(key) || {}
13
+ end
14
+ alias_method :to_hash, :all
15
+ alias_method :value, :all
16
+
17
+ # @param [String] field
18
+ # The field to delete
19
+ def delete(field)
20
+ conn.hdel key, field
21
+ end
22
+
23
+ # @return [Boolean] true, if field exists
24
+ def key?(field)
25
+ !!conn.hexists(key, field)
26
+ end
27
+ alias_method :has_key?, :key?
28
+ alias_method :member?, :key?
29
+ alias_method :include?, :key?
30
+
31
+ # @return [Boolean] true, if empty
32
+ def empty?
33
+ length.zero?
34
+ end
35
+
36
+ # @param [String] field
37
+ # The field to fetch
38
+ # @return [String] value stored in +field+
39
+ def fetch(field)
40
+ conn.hget key, field
41
+ end
42
+ alias_method :[], :fetch
43
+
44
+ # @param [String] field
45
+ # The field to store at
46
+ # @param [String] value
47
+ # The value to store
48
+ # @param [Hash] options
49
+ # The value to store
50
+ def store(field, value, options = {})
51
+ if value.nil?
52
+ delete field
53
+ else
54
+ conn.hset key, field, value
55
+ end
56
+ end
57
+ alias_method :[]=, :store
58
+
59
+ # @return [Array] all keys
60
+ def keys
61
+ conn.hkeys key
62
+ end
63
+
64
+ # @return [Array] all values
65
+ def values
66
+ conn.hvals key
67
+ end
68
+
69
+ # @return [Intege] the number of pairs in the hash
70
+ def length
71
+ conn.hlen key
72
+ end
73
+ alias_method :size, :length
74
+
75
+ # @param [String] field
76
+ # The field to increment
77
+ # @param [Integer] value
78
+ # The increment value, defaults to 1
79
+ def increment(field, value = 1)
80
+ conn.hincrby key, field, value
81
+ end
82
+
83
+ # @param [String] field
84
+ # The field to decrement
85
+ # @param [Integer] value
86
+ # The decrement value, defaults to 1
87
+ def decrement(field, value = 1)
88
+ increment(field, -value)
89
+ end
90
+
91
+ # @param [multiple] fields
92
+ # The field to return
93
+ # @return [Array] values
94
+ def values_at(*fields)
95
+ conn.hmget key, *fields
96
+ end
97
+
98
+ # @param [Hash] hash
99
+ # The pairs to update
100
+ def update(hash)
101
+ merge!(hash)
102
+ to_hash
103
+ end
104
+
105
+ # @param [Hash] hash
106
+ # The pairs to merge
107
+ def merge!(hash)
108
+ hash = hash.reject do |field, value|
109
+ delete(field) if value.nil?
110
+ end
111
+ conn.hmset key, *hash.flatten
112
+ end
113
+
114
+ # Comparator
115
+ # @return [Boolean] true if same as `other`
116
+ def ==(other)
117
+ other.respond_to?(:to_hash) && other.to_hash == to_hash
118
+ end
119
+
120
+ end
@@ -0,0 +1,144 @@
1
+ class Redpear::Store::List < Redpear::Store::Enumerable
2
+
3
+ # @yield over each item in the list
4
+ # @yieldparam [String] item
5
+ def each(&block)
6
+ all.each(&block)
7
+ end
8
+
9
+ # @return [Array] all items
10
+ def all
11
+ slice(0..-1)
12
+ end
13
+ alias_method :to_a, :all
14
+
15
+ # Returns a slice of the list
16
+ # @overload slice(index)
17
+ # Returns the item at `index`
18
+ # @param [Integer] index
19
+ # @return [String] item
20
+ # @overload slice(start, length)
21
+ # Returns from `length` items from `start`
22
+ # @param [Integer] start
23
+ # @param [Integer] length
24
+ # @return [Array] items
25
+ # @overload slice(range)
26
+ # Returns items from range
27
+ # @param [Range] range
28
+ # @return [Array] items
29
+ def slice(start, length = nil)
30
+ case start
31
+ when Integer
32
+ if length
33
+ range(start, start + length - 1)
34
+ else
35
+ conn.lindex(key, start) rescue nil
36
+ end
37
+ when Range
38
+ range *range_pair(start)
39
+ else
40
+ []
41
+ end
42
+ end
43
+ alias_method :[], :slice
44
+
45
+ # Destructive slice. Returns specified range and removes other items.
46
+ # @overload slice(start, length)
47
+ def slice!(start, length = nil)
48
+ case start
49
+ when Range
50
+ trim *range_pair(start)
51
+ else
52
+ trim(start, start + length - 1)
53
+ end
54
+ to_a
55
+ end
56
+
57
+ # @param [Integer] start
58
+ # @param [Integer] finish
59
+ # @return [Array] items
60
+ def range(start, finish)
61
+ conn.lrange(key, start, finish) || []
62
+ end
63
+
64
+ # @param [Integer] start
65
+ # @param [Integer] finish
66
+ def trim(start, finish)
67
+ conn.ltrim(key, start, finish)
68
+ end
69
+
70
+ # @return [Integer] the number of items in the set
71
+ def length
72
+ conn.llen key
73
+ end
74
+ alias_method :size, :length
75
+
76
+ # Appends a single item. Chainable example:
77
+ # list << 'a' << 'b'
78
+ # @param [String] item
79
+ # A item to add
80
+ def push(item)
81
+ conn.rpush key, item
82
+ self
83
+ end
84
+ alias_method :<<, :push
85
+
86
+ # Removes the last item
87
+ # @return [String] the removed item
88
+ def pop
89
+ conn.rpop key
90
+ end
91
+
92
+ # Prepends a single item.
93
+ # @param [String] item
94
+ # A item to add
95
+ def unshift(item)
96
+ conn.lpush key, item
97
+ self
98
+ end
99
+
100
+ # Removes the first item
101
+ # @return [String] the removed item
102
+ def shift
103
+ conn.lpop key
104
+ end
105
+
106
+ # Removes the last item and prepends it to `target`
107
+ # @param [Redpear::Store::List] target
108
+ def pop_unshift(target)
109
+ conn.rpoplpush key, target.to_s
110
+ end
111
+
112
+ # Comparator
113
+ # @param [#to_a] other
114
+ # @return [Boolean] true if items match
115
+ def ==(other)
116
+ other.respond_to?(:to_a) && other.to_a == to_a
117
+ end
118
+
119
+ # Remove `item` from list
120
+ # @param [String] item
121
+ # @param [Integer] count
122
+ # The number of items to remove.
123
+ # When =0 - remove all occurences
124
+ # When >0 - remove the first `count` items
125
+ # When <0 - remove the last `count` items
126
+ def delete(item, count = 0)
127
+ conn.lrem key, count, item
128
+ end
129
+
130
+ # Insert `item` before `pivot`
131
+ # @param [String] pivot
132
+ # @param [String] item
133
+ def insert_before(pivot, item)
134
+ conn.linsert key, :before, pivot, item
135
+ end
136
+
137
+ # Insert `item` after `pivot`
138
+ # @param [String] pivot
139
+ # @param [String] item
140
+ def insert_after(pivot, item)
141
+ conn.linsert key, :after, pivot, item
142
+ end
143
+
144
+ end
@@ -0,0 +1,108 @@
1
+ class Redpear::Store::Lock < Redpear::Store::Base
2
+ class LockTimeout < ::StandardError; end
3
+
4
+ class << self
5
+
6
+ # Default lock options. Override via e.g.:
7
+ #
8
+ # Redpear::Store::Lock.default_options[:lock_timeout] = 5
9
+ #
10
+ # @return [Hash] default options
11
+ def default_options
12
+ @default_options ||= { :lock_timeout => 2, :wait_timeout => 2 }
13
+ end
14
+
15
+ end
16
+
17
+ # @return [Float] the current lock timestamp
18
+ def current
19
+ value.to_f
20
+ end
21
+
22
+ # @return [String] the current lock value
23
+ def value
24
+ conn.get key
25
+ end
26
+
27
+ # Creates a lock and yields a transaction. Example:
28
+ #
29
+ # sender = Redpear::Store::Hash.new "accounts:sender", connection
30
+ # recipient = Redpear::Store::Hash.new "accounts:recipient", connection
31
+ # lock = Redpear::Store::Lock.new "locks:transfer", connection
32
+ #
33
+ # lock.lock do
34
+ # sender.decrement 'balance', 100
35
+ # recipient.increment 'balance', 100
36
+ # end
37
+ #
38
+ # @param [Hash] options
39
+ # @option [Integer] lock_timeout
40
+ # Hold the lock for a maximum of `lock_timeout` seconds. Defaults to 2.
41
+ # @option [Integer] wait_timeout
42
+ # Wait for `wait_timeout` seconds to obtain a lock, before timing out. Defaults to 2.
43
+ # @yield [] processes the block within the lock
44
+ def lock(options = {})
45
+ options = self.class.default_options.merge(options)
46
+ result = nil
47
+ timestamp = nil
48
+ timeout = to_time(options[:wait_timeout])
49
+
50
+ while !timestamp && timeout > Time.now
51
+ timestamp = to_time(options[:lock_timeout]).to_f
52
+
53
+ if lock_obtained?(timestamp) || expired_lock_obtained?(timestamp)
54
+ result = yield
55
+ else
56
+ timestamp = nil # Unset
57
+ sleep 0.1
58
+ end
59
+ end
60
+
61
+ unless timestamp
62
+ raise LockTimeout, "Could not acquire lock on '#{key}'. Timed out."
63
+ end
64
+
65
+ result
66
+ ensure
67
+ purge! if timestamp && timestamp > Time.now.to_f
68
+ end
69
+
70
+ protected
71
+
72
+ # @param [Float] timestamp
73
+ # The timestamp to set for the lock
74
+ # @return [Boolean]
75
+ # True, if timestamp could be set and the lock was obtained
76
+ def lock_obtained?(timestamp)
77
+ conn.setnx key, timestamp
78
+ end
79
+
80
+ # @param [Float] timestamp
81
+ # The timestamp to set for the lock
82
+ # @return [Boolean]
83
+ # True, if key is set to an expired value and we can replace it
84
+ # with our `timestamp`
85
+ def expired_lock_obtained?(timestamp)
86
+ exists? && past?(value) && past?(conn.getset(key, timestamp))
87
+ end
88
+
89
+ # @return [Boolean] true, if timestamp has expired and is in the past
90
+ def past?(timestamp)
91
+ timestamp = timestamp.to_f if timestamp.is_a?(String)
92
+ timestamp < Time.now.to_f
93
+ end
94
+
95
+ private
96
+
97
+ def to_time(value)
98
+ case value
99
+ when Time
100
+ value
101
+ when Numeric
102
+ Time.now + value
103
+ else
104
+ Time.now + 5
105
+ end
106
+ end
107
+
108
+ end