redis-objects 0.8.0 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
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