redpear 0.6.4 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/redpear/connection.rb +110 -17
- data/lib/redpear/model/expiration.rb +18 -0
- data/lib/redpear/model/factory_girl.rb +27 -0
- data/lib/redpear/model/finders.rb +37 -0
- data/lib/redpear/model/machinist.rb +54 -0
- data/lib/redpear/model.rb +186 -63
- data/lib/redpear/schema/collection.rb +7 -7
- data/lib/redpear/{column.rb → schema/column.rb} +12 -6
- data/lib/redpear/schema/index.rb +22 -0
- data/lib/redpear/schema/score.rb +13 -0
- data/lib/redpear/schema.rb +9 -6
- data/lib/redpear/store/base.rb +120 -0
- data/lib/redpear/store/counter.rb +44 -0
- data/lib/redpear/store/enumerable.rb +8 -0
- data/lib/redpear/store/hash.rb +120 -0
- data/lib/redpear/store/list.rb +144 -0
- data/lib/redpear/store/lock.rb +108 -0
- data/lib/redpear/store/set.rb +147 -0
- data/lib/redpear/store/sorted_set.rb +239 -0
- data/lib/redpear/store/value.rb +66 -0
- data/lib/redpear/store.rb +11 -0
- data/lib/redpear.rb +7 -22
- metadata +45 -30
- data/lib/redpear/core_ext/stringify_keys.rb +0 -17
- data/lib/redpear/counters.rb +0 -26
- data/lib/redpear/expiration.rb +0 -27
- data/lib/redpear/finders.rb +0 -59
- data/lib/redpear/index.rb +0 -31
- data/lib/redpear/machinist.rb +0 -51
- data/lib/redpear/members.rb +0 -83
- data/lib/redpear/namespace.rb +0 -79
- data/lib/redpear/nest.rb +0 -100
- data/lib/redpear/persistence.rb +0 -147
- data/lib/redpear/zindex.rb +0 -26
- data/lib/redpear/zmembers.rb +0 -68
data/lib/redpear/schema.rb
CHANGED
@@ -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
|
28
|
-
columns.store(Redpear::
|
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,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
|