redis-objects 0.8.0 → 0.9.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 5c9dea664a6595b39e8542280a5b91091d204d72
4
- data.tar.gz: 953f0b08f429ee68d58bb50d4db16c2f93840781
3
+ metadata.gz: 95f1e710f5708a5b30fb2a843fcfdacaff0af044
4
+ data.tar.gz: 1d4816620d04019576ae1e277be3c5676b0c5be0
5
5
  SHA512:
6
- metadata.gz: 7ad7b1a216ff428b194bc2b6156f5fde907cbed35dd93780e3193b69cee1306f1b66a20cb3e952fbee3280b2acf4e9a1b7d83c3caea56e95f616a9f962e2bdc9
7
- data.tar.gz: a08b2a851e6390ab78e9ca8102efab41784468aa2fbc9f3471327b13f271dc3ab981a4c055963de4ad4ad397fe7539826bef3d1f90af887bc620986ebcecd72e
6
+ metadata.gz: d68499ab0eafd74dcd51838f1723ee14d561bd772725dbef69ce26d86dc66bca412c7520be5f02c5cad9b761a77670e9c8707f5b34737409ada232c99b5a44d4
7
+ data.tar.gz: 0a59d05c7d974f0e7d20f5f938b6451a582ec5c025d450cb85073ff328b9abc19d250971f13fe6e2b191f3f3e7c6312a3f81f8f10e93c1b47c9459b12680f775
data/CHANGELOG.rdoc CHANGED
@@ -1,5 +1,25 @@
1
1
  = Changelog for Redis::Objects
2
2
 
3
+ == 0.9.0 (6 Feb 2014)
4
+
5
+ * Ensure we don't double-unmarshal values, which could be a security issue [markijbema, nateware]
6
+
7
+ * Support a custom redis connection per redis key [hfwang]
8
+
9
+ * HashKey#fetch now behaves more similarly to Ruby [datapimp]
10
+
11
+ * Add incrbyfloat and decrbyfloat for values and hashes [nateware]
12
+
13
+ * Add support for @sorted_set.merge and variadic zadd [hfwang]
14
+
15
+ * Add support for srandmember for sets [LYY]
16
+
17
+ * Handle @set << [] in an intelligent way [kitchen]
18
+
19
+ * Add multi-unshift functionality [nateware]
20
+
21
+ * Additional test coverage for @sorted_set.merge and other ops [nateware]
22
+
3
23
  == 0.8.0 (9 Nov 2013)
4
24
 
5
25
  * Refactor to modular include/extend approach to enable hooking and remove evals [nateware]
data/README.md CHANGED
@@ -452,6 +452,13 @@ lock time.
452
452
  Keep in mind that true locks serialize your entire application at that point. As
453
453
  such, atomic counters are strongly preferred.
454
454
 
455
+ ### Expiration ###
456
+
457
+ Use :expiration and :expireat options to set default expiration.
458
+
459
+ value :value_with_expiration, :expiration => 1.hour
460
+ value :value_with_expireat, :expireat => Time.now + 1.hour
461
+
455
462
  Author
456
463
  =======
457
464
  Copyright (c) 2009-2013 [Nate Wiger](http://nateware.com). All Rights Reserved.
@@ -13,5 +13,37 @@ class Redis
13
13
  end
14
14
 
15
15
  alias :inspect :to_s # Ruby 1.9.2
16
+
17
+ def set_expiration
18
+ if !@options[:expiration].nil?
19
+ redis.expire(@key, @options[:expiration]) if redis.ttl(@key) < 0
20
+ elsif !@options[:expireat].nil?
21
+ redis.expireat(@key, @options[:expireat].to_i) if redis.ttl(@key) < 0
22
+ end
23
+ end
24
+
25
+ class << self
26
+ def expiration_filter(*names)
27
+ names.each do |name|
28
+ if ['=', '?', '!'].include? name.to_s[-1]
29
+ with_name = "#{name[0..-2]}_with_expiration#{name[-1]}".to_sym
30
+ without_name = "#{name[0..-2]}_without_expiration#{name[-1]}".to_sym
31
+ else
32
+ with_name = "#{name}_with_expiration".to_sym
33
+ without_name = "#{name}_without_expiration".to_sym
34
+ end
35
+
36
+ alias_method without_name, name
37
+
38
+ define_method(with_name) do |*args|
39
+ result = send(without_name, *args)
40
+ set_expiration
41
+ result
42
+ end
43
+
44
+ alias_method name, with_name
45
+ end
46
+ end
47
+ end
16
48
  end
17
49
  end
data/lib/redis/counter.rb CHANGED
@@ -16,6 +16,7 @@ class Redis
16
16
  def initialize(key, *args)
17
17
  super(key, *args)
18
18
  @options[:start] ||= 0
19
+ raise ArgumentError, "Marshalling redis counters does not make sense" if @options[:marshal]
19
20
  redis.setnx(key, @options[:start]) unless @options[:start] == 0 || @options[:init] === false
20
21
  end
21
22
 
@@ -45,6 +46,20 @@ class Redis
45
46
  end
46
47
  alias_method :get, :value
47
48
 
49
+ def value=(val)
50
+ if val.nil?
51
+ delete
52
+ else
53
+ redis.set key, val
54
+ end
55
+ end
56
+ alias_method :set, :value=
57
+
58
+ # Like .value but casts to float since Redis addresses these differently.
59
+ def to_f
60
+ redis.get(key).to_f
61
+ end
62
+
48
63
  # Increment the counter atomically and return the new value. If passed
49
64
  # a block, that block will be evaluated with the new value of the counter
50
65
  # as an argument. If the block returns nil or throws an exception, the
@@ -55,6 +70,7 @@ class Redis
55
70
  block_given? ? rewindable_block(:decrement, by, val, &block) : val
56
71
  end
57
72
  alias_method :incr, :increment
73
+ alias_method :incrby, :increment
58
74
 
59
75
  # Decrement the counter atomically and return the new value. If passed
60
76
  # a block, that block will be evaluated with the new value of the counter
@@ -66,6 +82,21 @@ class Redis
66
82
  block_given? ? rewindable_block(:increment, by, val, &block) : val
67
83
  end
68
84
  alias_method :decr, :decrement
85
+ alias_method :decrby, :decrement
86
+
87
+ # Increment a floating point counter atomically.
88
+ # Redis uses separate API's to interact with integers vs floats.
89
+ def incrbyfloat(by=1.0, &block)
90
+ val = redis.incrbyfloat(key, by).to_f
91
+ block_given? ? rewindable_block(:decrbyfloat, by, val, &block) : val
92
+ end
93
+
94
+ # Decrement a floating point counter atomically.
95
+ # Redis uses separate API's to interact with integers vs floats.
96
+ def decrbyfloat(by=1.0, &block)
97
+ val = redis.incrbyfloat(key, -by).to_f
98
+ block_given? ? rewindable_block(:incrbyfloat, -by, val, &block) : val
99
+ end
69
100
 
70
101
  ##
71
102
  # Proxy methods to help make @object.counter == 10 work
@@ -81,6 +112,8 @@ class Redis
81
112
  end
82
113
  EndOverload
83
114
  end
115
+
116
+ expiration_filter :increment, :decrement
84
117
 
85
118
  private
86
119
 
@@ -9,8 +9,6 @@ class Redis
9
9
  include Enumerable
10
10
  require 'redis/helpers/core_commands'
11
11
  include Redis::Helpers::CoreCommands
12
- require 'redis/helpers/serialize'
13
- include Redis::Helpers::Serialize
14
12
 
15
13
  attr_reader :key, :options
16
14
  def initialize(key, *args)
@@ -18,30 +16,18 @@ class Redis
18
16
  @options[:marshal_keys] ||= {}
19
17
  end
20
18
 
21
- # Needed since Redis::Hash masks bare Hash in redis.rb
22
- def self.[](*args)
23
- ::Hash[*args]
24
- end
25
-
26
- # Sets a field to value
27
- def []=(field, value)
28
- store(field, to_redis(value))
29
- end
30
-
31
- # Gets the value of a field
32
- def [](field)
33
- fetch(field)
34
- end
35
-
36
19
  # Redis: HSET
37
20
  def store(field, value)
38
- redis.hset(key, field, to_redis(value, options[:marshal_keys][field]))
21
+ redis.hset(key, field, marshal(value, options[:marshal_keys][field]))
39
22
  end
23
+ alias_method :[]=, :store
40
24
 
41
25
  # Redis: HGET
42
- def fetch(field)
43
- from_redis redis.hget(key, field), options[:marshal_keys][field]
26
+ def hget(field)
27
+ unmarshal redis.hget(key, field), options[:marshal_keys][field]
44
28
  end
29
+ alias_method :get, :hget
30
+ alias_method :[], :hget
45
31
 
46
32
  # Verify that a field exists. Redis: HEXISTS
47
33
  def has_key?(field)
@@ -56,6 +42,16 @@ class Redis
56
42
  redis.hdel(key, field)
57
43
  end
58
44
 
45
+ # Fetch a key in a way similar to Ruby's Hash#fetch
46
+ def fetch(field, *args, &block)
47
+ value = hget(field)
48
+ default = args[0]
49
+
50
+ return value if value || (!default && !block_given?)
51
+
52
+ block_given? ? block.call(field) : default
53
+ end
54
+
59
55
  # Return all the keys of the hash. Redis: HKEYS
60
56
  def keys
61
57
  redis.hkeys(key)
@@ -63,14 +59,14 @@ class Redis
63
59
 
64
60
  # Return all the values of the hash. Redis: HVALS
65
61
  def values
66
- from_redis redis.hvals(key)
62
+ redis.hvals(key).map{|v| unmarshal(v) }
67
63
  end
68
64
  alias_method :vals, :values
69
65
 
70
66
  # Retrieve the entire hash. Redis: HGETALL
71
67
  def all
72
68
  h = redis.hgetall(key) || {}
73
- h.each { |k,v| h[k] = from_redis(v, options[:marshal_keys][k]) }
69
+ h.each{|k,v| h[k] = unmarshal(v, options[:marshal_keys][k]) }
74
70
  h
75
71
  end
76
72
  alias_method :clone, :all
@@ -111,7 +107,7 @@ class Redis
111
107
  def bulk_set(*args)
112
108
  raise ArgumentError, "Argument to bulk_set must be hash of key/value pairs" unless args.last.is_a?(::Hash)
113
109
  redis.hmset(key, *args.last.inject([]){ |arr,kv|
114
- arr + [kv[0], to_redis(kv[1], options[:marshal_keys][kv[0]])]
110
+ arr + [kv[0], marshal(kv[1], options[:marshal_keys][kv[0]])]
115
111
  })
116
112
  end
117
113
  alias_method :update, :bulk_set
@@ -120,7 +116,7 @@ class Redis
120
116
  def fill(pairs={})
121
117
  raise ArgumentError, "Arugment to fill must be a hash of key/value pairs" unless pairs.is_a?(::Hash)
122
118
  pairs.each do |field, value|
123
- redis.hsetnx(key, field, to_redis(value, options[:marshal_keys][field]))
119
+ redis.hsetnx(key, field, marshal(value, options[:marshal_keys][field]))
124
120
  end
125
121
  end
126
122
 
@@ -129,7 +125,7 @@ class Redis
129
125
  hsh = {}
130
126
  res = redis.hmget(key, *fields.flatten)
131
127
  fields.each do |k|
132
- hsh[k] = from_redis(res.shift, options[:marshal_keys][k])
128
+ hsh[k] = unmarshal(res.shift, options[:marshal_keys][k])
133
129
  end
134
130
  hsh
135
131
  end
@@ -138,12 +134,12 @@ class Redis
138
134
  # Values are returned in a collection in the same order than their keys in *keys Redis: HMGET
139
135
  def bulk_values(*keys)
140
136
  res = redis.hmget(key, *keys.flatten)
141
- keys.inject([]){|collection, k| collection << from_redis(res.shift)}
137
+ keys.inject([]){|collection, k| collection << unmarshal(res.shift, options[:marshal_keys][k])}
142
138
  end
143
139
 
144
140
  # Increment value by integer at field. Redis: HINCRBY
145
- def incrby(field, val = 1)
146
- ret = redis.hincrby(key, field, val)
141
+ def incrby(field, by=1)
142
+ ret = redis.hincrby(key, field, by)
147
143
  unless ret.is_a? Array
148
144
  ret.to_i
149
145
  else
@@ -152,6 +148,28 @@ class Redis
152
148
  end
153
149
  alias_method :incr, :incrby
154
150
 
151
+ # Decrement value by integer at field. Redis: HINCRBY
152
+ def decrby(field, by=1)
153
+ incrby(field, -by)
154
+ end
155
+ alias_method :decr, :decrby
156
+
157
+ # Increment value by float at field. Redis: HINCRBYFLOAT
158
+ def incrbyfloat(field, by=1.0)
159
+ ret = redis.hincrbyfloat(key, field, by)
160
+ unless ret.is_a? Array
161
+ ret.to_i
162
+ else
163
+ nil
164
+ end
165
+ end
166
+
167
+ # Decrement value by float at field. Redis: HINCRBYFLOAT
168
+ def decrbyfloat(field, by=1.0)
169
+ incrbyfloat(field, -by)
170
+ end
171
+
172
+ expiration_filter :[]=, :store, :bulk_set, :fill, :incrby
155
173
  end
156
174
  end
157
175
 
@@ -52,9 +52,29 @@ class Redis
52
52
 
53
53
  def sort(options={})
54
54
  options[:order] = "asc alpha" if options.keys.count == 0 # compat with Ruby
55
- redis.sort(key, options)
55
+ val = redis.sort(key, options)
56
+ val.is_a?(Array) ? val.map{|v| unmarshal(v)} : val
56
57
  end
57
- end
58
58
 
59
+ def marshal(value, domarshal=false)
60
+ if value.nil?
61
+ nil
62
+ elsif options[:marshal] || domarshal
63
+ Marshal.dump(value)
64
+ else
65
+ value
66
+ end
67
+ end
68
+
69
+ def unmarshal(value, domarshal=false)
70
+ if value.nil?
71
+ nil
72
+ elsif options[:marshal] || domarshal
73
+ Marshal.load(value)
74
+ else
75
+ value
76
+ end
77
+ end
78
+ end
59
79
  end
60
80
  end
data/lib/redis/list.rb CHANGED
@@ -10,31 +10,29 @@ class Redis
10
10
  include Enumerable
11
11
  require 'redis/helpers/core_commands'
12
12
  include Redis::Helpers::CoreCommands
13
- require 'redis/helpers/serialize'
14
- include Redis::Helpers::Serialize
15
13
 
16
14
  attr_reader :key, :options
17
15
 
18
16
  # Works like push. Can chain together: list << 'a' << 'b'
19
17
  def <<(value)
20
- push(value)
18
+ push(value) # marshal in push()
21
19
  self # for << 'a' << 'b'
22
20
  end
23
21
 
24
22
  # Add a member before or after pivot in the list. Redis: LINSERT
25
23
  def insert(where,pivot,value)
26
- redis.linsert(key,where,to_redis(pivot),to_redis(value))
24
+ redis.linsert(key,where,marshal(pivot),marshal(value))
27
25
  end
28
26
 
29
27
  # Add a member to the end of the list. Redis: RPUSH
30
28
  def push(*values)
31
- redis.rpush(key, values.map{|v| to_redis(v) })
29
+ redis.rpush(key, values.map{|v| marshal(v) })
32
30
  redis.ltrim(key, -options[:maxlength], -1) if options[:maxlength]
33
31
  end
34
32
 
35
33
  # Remove a member from the end of the list. Redis: RPOP
36
34
  def pop
37
- from_redis redis.rpop(key)
35
+ unmarshal redis.rpop(key)
38
36
  end
39
37
 
40
38
  # Atomically pops a value from one list, pushes to another and returns the
@@ -46,24 +44,24 @@ class Redis
46
44
  #
47
45
  # Redis: RPOPLPUSH
48
46
  def rpoplpush(destination)
49
- from_redis redis.rpoplpush(key, destination.is_a?(Redis::List) ? destination.key : destination.to_s)
47
+ unmarshal redis.rpoplpush(key, destination.is_a?(Redis::List) ? destination.key : destination.to_s)
50
48
  end
51
49
 
52
50
  # Add a member to the start of the list. Redis: LPUSH
53
51
  def unshift(*values)
54
- redis.lpush(key, values.map{|v| to_redis(v) })
52
+ redis.lpush(key, values.map{|v| marshal(v) })
55
53
  redis.ltrim(key, 0, options[:maxlength] - 1) if options[:maxlength]
56
54
  end
57
55
 
58
56
  # Remove a member from the start of the list. Redis: LPOP
59
57
  def shift
60
- from_redis redis.lpop(key)
58
+ unmarshal redis.lpop(key)
61
59
  end
62
60
 
63
61
  # Return all values in the list. Redis: LRANGE(0,-1)
64
62
  def values
65
- v = from_redis range(0, -1)
66
- v.nil? ? [] : v
63
+ vals = range(0, -1)
64
+ vals.nil? ? [] : vals
67
65
  end
68
66
  alias_method :get, :values
69
67
 
@@ -88,7 +86,7 @@ class Redis
88
86
 
89
87
  # Same functionality as Ruby arrays.
90
88
  def []=(index, value)
91
- redis.lset(key, index, to_redis(value))
89
+ redis.lset(key, index, marshal(value))
92
90
  end
93
91
 
94
92
  # Delete the element(s) from the list that match name. If count is specified,
@@ -96,7 +94,7 @@ class Redis
96
94
  # Use .del to completely delete the entire key.
97
95
  # Redis: LREM
98
96
  def delete(name, count=0)
99
- redis.lrem(key, count, to_redis(name)) # weird api
97
+ redis.lrem(key, count, marshal(name)) # weird api
100
98
  end
101
99
 
102
100
  # Iterate through each member of the set. Redis::Objects mixes in Enumerable,
@@ -108,13 +106,13 @@ class Redis
108
106
  # Return a range of values from +start_index+ to +end_index+. Can also use
109
107
  # the familiar list[start,end] Ruby syntax. Redis: LRANGE
110
108
  def range(start_index, end_index)
111
- from_redis redis.lrange(key, start_index, end_index)
109
+ redis.lrange(key, start_index, end_index).map{|v| unmarshal(v) }
112
110
  end
113
111
 
114
112
  # Return the value at the given index. Can also use familiar list[index] syntax.
115
113
  # Redis: LINDEX
116
114
  def at(index)
117
- from_redis redis.lindex(key, index)
115
+ unmarshal redis.lindex(key, index)
118
116
  end
119
117
 
120
118
  # Return the first element in the list. Redis: LINDEX(0)
@@ -145,5 +143,7 @@ class Redis
145
143
  def to_s
146
144
  values.join(', ')
147
145
  end
146
+
147
+ expiration_filter :[]=, :push, :insert, :unshift
148
148
  end
149
149
  end
data/lib/redis/objects.rb CHANGED
@@ -109,6 +109,11 @@ class Redis
109
109
  downcase
110
110
  end
111
111
 
112
+ def redis_field_redis(name) #:nodoc:
113
+ klass = first_ancestor_with(name)
114
+ return klass.redis_objects[name.to_sym][:redis] || self.redis
115
+ end
116
+
112
117
  def redis_field_key(name, id=nil, context=self) #:nodoc:
113
118
  klass = first_ancestor_with(name)
114
119
  # READ THIS: This can never ever ever ever change or upgrades will corrupt all data
@@ -148,8 +153,13 @@ class Redis
148
153
  def redis() self.class.redis end
149
154
  def redis_objects() self.class.redis_objects end
150
155
 
156
+ def redis_field_redis(name) #:nodoc:
157
+ return self.class.redis_field_redis(name)
158
+ end
159
+
151
160
  def redis_field_key(name) #:nodoc:
152
- self.class.redis_field_key(name, send(self.class.redis_id_field), self)
161
+ id = send(self.class.redis_id_field)
162
+ self.class.redis_field_key(name, id, self)
153
163
  end
154
164
  end
155
165
  end