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 +4 -4
- data/CHANGELOG.rdoc +20 -0
- data/README.md +7 -0
- data/lib/redis/base_object.rb +32 -0
- data/lib/redis/counter.rb +33 -0
- data/lib/redis/hash_key.rb +46 -28
- data/lib/redis/helpers/core_commands.rb +22 -2
- data/lib/redis/list.rb +15 -15
- data/lib/redis/objects.rb +11 -1
- data/lib/redis/objects/counters.rb +1 -1
- data/lib/redis/objects/hashes.rb +1 -1
- data/lib/redis/objects/lists.rb +1 -1
- data/lib/redis/objects/locks.rb +2 -2
- data/lib/redis/objects/sets.rb +1 -1
- data/lib/redis/objects/sorted_sets.rb +1 -1
- data/lib/redis/objects/values.rb +1 -1
- data/lib/redis/objects/version.rb +1 -1
- data/lib/redis/set.rb +25 -20
- data/lib/redis/sorted_set.rb +32 -22
- data/lib/redis/value.rb +5 -5
- data/spec/redis_objects_conn_spec.rb +32 -2
- data/spec/redis_objects_instance_spec.rb +171 -2
- data/spec/redis_objects_model_spec.rb +70 -4
- data/spec/spec_helper.rb +21 -16
- metadata +2 -3
- data/lib/redis/helpers/serialize.rb +0 -41
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 95f1e710f5708a5b30fb2a843fcfdacaff0af044
|
4
|
+
data.tar.gz: 1d4816620d04019576ae1e277be3c5676b0c5be0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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.
|
data/lib/redis/base_object.rb
CHANGED
@@ -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
|
|
data/lib/redis/hash_key.rb
CHANGED
@@ -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,
|
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
|
43
|
-
|
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
|
-
|
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
|
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],
|
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,
|
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] =
|
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 <<
|
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,
|
146
|
-
ret = redis.hincrby(key, field,
|
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,
|
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|
|
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
|
-
|
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
|
-
|
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|
|
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
|
-
|
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
|
-
|
66
|
-
|
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,
|
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,
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|