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 +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
|