redis-native_hash 0.1.0 → 0.2.1
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.
- data/README.mkd +143 -8
- data/TODO.mkd +58 -0
- data/VERSION +1 -1
- data/lib/action_dispatch/session/redis_hash.rb +4 -0
- data/lib/active_support/cache/redis_hash.rb +48 -0
- data/lib/active_support/cache/redis_store.rb +46 -0
- data/lib/rack/session/redis_hash.rb +61 -26
- data/lib/redis/big_hash.rb +105 -0
- data/lib/redis/client_helper.rb +35 -0
- data/lib/redis/key_helper.rb +24 -0
- data/lib/redis/lazy_hash.rb +64 -0
- data/lib/redis/marshal.rb +2 -1
- data/lib/redis/native_hash.rb +93 -106
- data/lib/redis/tracked_hash.rb +20 -9
- data/lib/redis_hash.rb +22 -0
- data/redis-native_hash.gemspec +20 -9
- data/spec/redis/big_hash_spec.rb +118 -0
- data/spec/redis/lazy_hash_spec.rb +160 -0
- data/spec/redis/{redis_hash_spec.rb → native_hash_spec.rb} +2 -1
- data/spec/spec_helper.rb +3 -1
- data/spec/user_defined/user_spec.rb +5 -1
- metadata +26 -17
data/lib/redis/native_hash.rb
CHANGED
@@ -1,30 +1,24 @@
|
|
1
|
-
|
2
|
-
require 'core_ext/hash' unless defined?(ActiveSupport)
|
3
|
-
require 'redis/marshal'
|
4
|
-
require 'redis/tracked_hash'
|
5
|
-
|
6
|
-
if defined?(Rack::Session)
|
7
|
-
require "rack/session/abstract/id"
|
8
|
-
require 'rack/session/redis_hash'
|
9
|
-
end
|
1
|
+
require_relative "../redis_hash"
|
10
2
|
|
11
3
|
require 'securerandom'
|
12
4
|
|
13
5
|
class Redis
|
14
6
|
class NativeHash < TrackedHash
|
7
|
+
include ClientHelper
|
8
|
+
include KeyHelper
|
15
9
|
|
16
10
|
attr_accessor :namespace
|
17
11
|
|
18
|
-
def initialize(
|
12
|
+
def initialize(aargh = nil)
|
19
13
|
super(nil)
|
20
|
-
|
21
|
-
|
22
|
-
self.namespace =
|
23
|
-
|
24
|
-
self.namespace =
|
14
|
+
case aargh
|
15
|
+
when String,Symbol
|
16
|
+
self.namespace = aargh
|
17
|
+
when Hash
|
18
|
+
self.namespace = aargh.keys.first
|
19
|
+
self.key = aargh.values.first
|
25
20
|
end
|
26
|
-
|
27
|
-
update(data) if data.kind_of?(Hash)
|
21
|
+
track!
|
28
22
|
end
|
29
23
|
|
30
24
|
def []=(key, value)
|
@@ -55,44 +49,44 @@ class Redis
|
|
55
49
|
indices.collect { |key| self[ convert_key(key) ] }
|
56
50
|
end
|
57
51
|
|
58
|
-
def key
|
59
|
-
@key ||= self.class.generate_key
|
60
|
-
end
|
61
|
-
|
62
52
|
def key=(new_key)
|
63
53
|
renew_key(new_key)
|
64
54
|
end
|
65
55
|
|
66
56
|
attr_writer :version
|
67
57
|
def version
|
68
|
-
@version ||=
|
69
|
-
end
|
70
|
-
|
71
|
-
def save(
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
58
|
+
@version ||= generate_key
|
59
|
+
end
|
60
|
+
|
61
|
+
def save( max_attempts = 5 )
|
62
|
+
(1..max_attempts).each do |n|
|
63
|
+
redis.watch redis_key
|
64
|
+
latest_version = redis.hget(redis_key, "__version")
|
65
|
+
reload! unless ( latest_version.nil? || latest_version == self.version )
|
66
|
+
self.version = nil # generate new version token
|
67
|
+
changed_keys = (self.changed + self.added).uniq
|
68
|
+
changes = []
|
69
|
+
changed_keys.each do |key|
|
70
|
+
changes.push( key, Redis::Marshal.dump(self[key]) )
|
71
|
+
end
|
72
|
+
deleted_keys = self.deleted
|
73
|
+
if deleted_keys.empty? and changes.empty?
|
74
|
+
redis.unwatch
|
75
|
+
return true
|
76
|
+
end
|
84
77
|
success = redis.multi do
|
85
78
|
redis.hmset( redis_key, *changes.push("__version", self.version) ) unless changes.empty?
|
86
79
|
deleted_keys.each { |key| redis.hdel( redis_key, key) }
|
87
80
|
end
|
88
81
|
if success
|
89
|
-
untrack!; track! #reset
|
90
|
-
|
91
|
-
save( attempt + 1 )
|
82
|
+
untrack!; track! #reset hash
|
83
|
+
return true
|
92
84
|
end
|
93
|
-
else
|
94
|
-
redis.unwatch
|
95
85
|
end
|
86
|
+
raise "Unable to save hash after max attempts (#{max_attempts}). " +
|
87
|
+
"Amazing concurrency event may be underway. " +
|
88
|
+
"Make some popcorn."
|
89
|
+
false
|
96
90
|
end
|
97
91
|
|
98
92
|
def update(data)
|
@@ -106,8 +100,14 @@ class Redis
|
|
106
100
|
super(data.stringify_keys!)
|
107
101
|
end
|
108
102
|
|
103
|
+
def replace(other_hash)
|
104
|
+
clear
|
105
|
+
update(other_hash)
|
106
|
+
end
|
107
|
+
|
109
108
|
def reload!
|
110
|
-
|
109
|
+
hash = self.class.find( namespace ? {namespace => key} : key )
|
110
|
+
self.update( hash ) if hash
|
111
111
|
end
|
112
112
|
alias_method :reload, :reload!
|
113
113
|
|
@@ -127,79 +127,66 @@ class Redis
|
|
127
127
|
key
|
128
128
|
end
|
129
129
|
|
130
|
-
def
|
131
|
-
|
132
|
-
end
|
133
|
-
|
134
|
-
def self.redis=(resource)
|
135
|
-
@@redis = resource
|
136
|
-
end
|
137
|
-
|
138
|
-
def self.generate_key
|
139
|
-
t = Time.now
|
140
|
-
t.strftime('%Y%m%d%H%M%S.') + t.usec.to_s.rjust(6,'0') + '.' + SecureRandom.hex(16)
|
130
|
+
def expire(seconds)
|
131
|
+
redis.expire(redis_key, seconds)
|
141
132
|
end
|
142
133
|
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
134
|
+
class << self
|
135
|
+
def find(params)
|
136
|
+
case params
|
137
|
+
when Hash
|
138
|
+
hashes = []
|
139
|
+
params.each_pair do |namespace, key|
|
140
|
+
result = fetch_values( "#{namespace}:#{key}" )
|
141
|
+
unless result.empty?
|
142
|
+
hashes << build(namespace,key,result)
|
143
|
+
end
|
151
144
|
end
|
145
|
+
unless hashes.empty?
|
146
|
+
hashes.size == 1 ? hashes.first : hashes
|
147
|
+
else
|
148
|
+
nil
|
149
|
+
end
|
150
|
+
when String,Symbol
|
151
|
+
unless self == Redis::NativeHash
|
152
|
+
namespace = self.new.namespace.to_s
|
153
|
+
namespace = "#{namespace}:" unless namespace.empty?
|
154
|
+
result = fetch_values( "#{namespace}#{params}" )
|
155
|
+
else
|
156
|
+
result = fetch_values(params)
|
157
|
+
end
|
158
|
+
result.empty? ? nil : build(nil,params,result)
|
152
159
|
end
|
153
|
-
unless hashes.empty?
|
154
|
-
hashes.size == 1 ? hashes.first : hashes
|
155
|
-
else
|
156
|
-
nil
|
157
|
-
end
|
158
|
-
when String
|
159
|
-
unless self.instance_of?(NativeHash)
|
160
|
-
result = fetch_values( "#{self.new.namespace}:#{params}" )
|
161
|
-
else
|
162
|
-
result = fetch_values(params)
|
163
|
-
end
|
164
|
-
result.empty? ? nil : build(nil,params,result)
|
165
160
|
end
|
166
|
-
end
|
167
|
-
|
168
|
-
def self.build(namespace, key, values)
|
169
|
-
h = self.new
|
170
|
-
h.namespace = namespace
|
171
|
-
h.key = key
|
172
|
-
h.populate(values)
|
173
|
-
h
|
174
|
-
end
|
175
|
-
|
176
|
-
def self.fetch_values(key)
|
177
|
-
results = redis.hgetall(key)
|
178
|
-
results.each_pair { |key,value| results[key] = Redis::Marshal.load(value) }
|
179
|
-
end
|
180
161
|
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
def #{attr}
|
189
|
-
self["#{attr}"]
|
190
|
-
end
|
191
|
-
EOS
|
162
|
+
def build(namespace, key, values)
|
163
|
+
h = self.new
|
164
|
+
h.namespace = namespace
|
165
|
+
h.key = key
|
166
|
+
h.populate(values)
|
167
|
+
h
|
192
168
|
end
|
193
|
-
end
|
194
169
|
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
namespace.nil? ? key : "#{namespace}:#{key}"
|
170
|
+
def fetch_values(key)
|
171
|
+
results = redis.hgetall(key)
|
172
|
+
results.each_pair { |key,value| results[key] = Redis::Marshal.load(value) }
|
199
173
|
end
|
200
|
-
|
201
|
-
|
174
|
+
|
175
|
+
def attr_persist(*attributes)
|
176
|
+
attributes.each do |attr|
|
177
|
+
class_eval <<-EOS
|
178
|
+
def #{attr}=(value)
|
179
|
+
self["#{attr}"] = value
|
180
|
+
end
|
181
|
+
|
182
|
+
def #{attr}
|
183
|
+
self["#{attr}"]
|
184
|
+
end
|
185
|
+
EOS
|
186
|
+
end
|
202
187
|
end
|
188
|
+
|
189
|
+
end
|
203
190
|
end
|
204
191
|
end
|
205
192
|
|
data/lib/redis/tracked_hash.rb
CHANGED
@@ -1,42 +1,52 @@
|
|
1
1
|
class Redis
|
2
2
|
class TrackedHash < Hash
|
3
|
-
|
3
|
+
|
4
4
|
def original
|
5
5
|
@original ||= self.dup
|
6
6
|
end
|
7
7
|
alias_method :track, :original
|
8
8
|
alias_method :track!, :original
|
9
|
-
|
9
|
+
|
10
10
|
def untrack
|
11
11
|
@original = nil
|
12
12
|
end
|
13
13
|
alias_method :untrack!, :untrack
|
14
|
-
|
14
|
+
|
15
15
|
def retrack
|
16
16
|
untrack!
|
17
17
|
track!
|
18
18
|
end
|
19
19
|
alias_method :retrack!, :retrack
|
20
|
-
|
20
|
+
|
21
21
|
def changed
|
22
22
|
changes = keys.select do |key|
|
23
23
|
self[key] != original[key]
|
24
24
|
end
|
25
25
|
end
|
26
|
-
|
26
|
+
|
27
27
|
def deleted
|
28
28
|
original.keys - self.keys
|
29
29
|
end
|
30
|
-
|
30
|
+
|
31
31
|
def added
|
32
32
|
self.keys - original.keys
|
33
33
|
end
|
34
|
-
|
34
|
+
|
35
35
|
def populate(other_hash)
|
36
36
|
update(other_hash)
|
37
37
|
retrack!
|
38
38
|
end
|
39
|
-
|
39
|
+
|
40
|
+
def dup
|
41
|
+
dupe = super
|
42
|
+
# duplicate a little deeper
|
43
|
+
# otherwise, object references will make it appear a value hasn't changed when it has
|
44
|
+
self.keys.each do |k|
|
45
|
+
dupe[k] = self[k].dup rescue self[k]
|
46
|
+
end
|
47
|
+
dupe
|
48
|
+
end
|
49
|
+
|
40
50
|
def update(other_hash)
|
41
51
|
if other_hash.kind_of?(TrackedHash)
|
42
52
|
other_original = other_hash.original
|
@@ -51,6 +61,7 @@ class Redis
|
|
51
61
|
end
|
52
62
|
end
|
53
63
|
alias_method :merge!, :update
|
54
|
-
|
64
|
+
|
55
65
|
end
|
56
66
|
end
|
67
|
+
|
data/lib/redis_hash.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'redis'
|
2
|
+
require 'core_ext/hash' unless defined?(ActiveSupport)
|
3
|
+
require 'redis/marshal'
|
4
|
+
require 'redis/tracked_hash'
|
5
|
+
require 'redis/client_helper'
|
6
|
+
require 'redis/key_helper'
|
7
|
+
require 'redis/big_hash'
|
8
|
+
require 'redis/native_hash'
|
9
|
+
require 'redis/lazy_hash'
|
10
|
+
|
11
|
+
if defined?(Rack::Session)
|
12
|
+
require "rack/session/abstract/id"
|
13
|
+
require 'rack/session/redis_hash'
|
14
|
+
end
|
15
|
+
|
16
|
+
if defined?(ActionDispatch::Session)
|
17
|
+
require 'action_dispatch/session/redis_hash'
|
18
|
+
end
|
19
|
+
|
20
|
+
if defined?(ActiveSupport)
|
21
|
+
require "active_support/cache/redis_store"
|
22
|
+
end
|
data/redis-native_hash.gemspec
CHANGED
@@ -4,14 +4,14 @@
|
|
4
4
|
# -*- encoding: utf-8 -*-
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
|
-
s.name =
|
8
|
-
s.version = "0.1
|
7
|
+
s.name = "redis-native_hash"
|
8
|
+
s.version = "0.2.1"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Carl Zulauf", "Adam Lassek"]
|
12
|
-
s.date =
|
13
|
-
s.description =
|
14
|
-
s.email =
|
12
|
+
s.date = "2012-05-13"
|
13
|
+
s.description = "ruby-hash-to-redis-hash mapping"
|
14
|
+
s.email = "czulauf@lyconic.com"
|
15
15
|
s.extra_rdoc_files = [
|
16
16
|
"LICENSE.txt",
|
17
17
|
"README.mkd"
|
@@ -24,24 +24,35 @@ Gem::Specification.new do |s|
|
|
24
24
|
"LICENSE.txt",
|
25
25
|
"README.mkd",
|
26
26
|
"Rakefile",
|
27
|
+
"TODO.mkd",
|
27
28
|
"VERSION",
|
29
|
+
"lib/action_dispatch/session/redis_hash.rb",
|
30
|
+
"lib/active_support/cache/redis_hash.rb",
|
31
|
+
"lib/active_support/cache/redis_store.rb",
|
28
32
|
"lib/core_ext/hash.rb",
|
29
33
|
"lib/rack/session/redis_hash.rb",
|
34
|
+
"lib/redis/big_hash.rb",
|
35
|
+
"lib/redis/client_helper.rb",
|
36
|
+
"lib/redis/key_helper.rb",
|
37
|
+
"lib/redis/lazy_hash.rb",
|
30
38
|
"lib/redis/marshal.rb",
|
31
39
|
"lib/redis/native_hash.rb",
|
32
40
|
"lib/redis/tracked_hash.rb",
|
41
|
+
"lib/redis_hash.rb",
|
33
42
|
"redis-native_hash.gemspec",
|
34
43
|
"spec/redis-hash_spec.rb",
|
35
|
-
"spec/redis/
|
44
|
+
"spec/redis/big_hash_spec.rb",
|
45
|
+
"spec/redis/lazy_hash_spec.rb",
|
46
|
+
"spec/redis/native_hash_spec.rb",
|
36
47
|
"spec/spec_helper.rb",
|
37
48
|
"spec/tracked_hash_spec.rb",
|
38
49
|
"spec/user_defined/user_spec.rb"
|
39
50
|
]
|
40
|
-
s.homepage =
|
51
|
+
s.homepage = "http://github.com/carlzulauf/redis-native_hash"
|
41
52
|
s.licenses = ["MIT"]
|
42
53
|
s.require_paths = ["lib"]
|
43
|
-
s.rubygems_version =
|
44
|
-
s.summary =
|
54
|
+
s.rubygems_version = "1.8.15"
|
55
|
+
s.summary = "ruby-hash-to-redis-hash mapping"
|
45
56
|
|
46
57
|
if s.respond_to? :specification_version then
|
47
58
|
s.specification_version = 3
|
@@ -0,0 +1,118 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe Redis::BigHash do
|
4
|
+
before :each do
|
5
|
+
@hash = Redis::BigHash.new
|
6
|
+
@hash[:foo] = "bar"
|
7
|
+
@hash[:yin] = "yang"
|
8
|
+
end
|
9
|
+
|
10
|
+
describe "#[]" do
|
11
|
+
it "should read an existing value" do
|
12
|
+
@hash[:foo].should == "bar"
|
13
|
+
end
|
14
|
+
it "should get nil for a value that doesn't exist" do
|
15
|
+
@hash[:bad_key].should be_nil
|
16
|
+
end
|
17
|
+
it "should allow lookup of multiple keys, returning an array" do
|
18
|
+
@hash[:foo, :yin, :bad_key].should == ["bar", "yang", nil]
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe "#[]=" do
|
23
|
+
it "should store string values" do
|
24
|
+
str = "This is a test string"
|
25
|
+
@hash[:test] = str
|
26
|
+
@hash[:test].should == str
|
27
|
+
end
|
28
|
+
it "should store arbitrary objects" do
|
29
|
+
t = Time.now
|
30
|
+
@hash[:test] = t
|
31
|
+
@hash[:test].should == t
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
describe "#add" do
|
36
|
+
it "should not overwrite an existing value" do
|
37
|
+
@hash.add(:foo, "bad value")
|
38
|
+
@hash[:foo].should == "bar"
|
39
|
+
end
|
40
|
+
it "should set a value when it doesn't exist" do
|
41
|
+
@hash.add(:new_key, "good value")
|
42
|
+
@hash[:new_key].should == "good value"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
describe "#key=" do
|
47
|
+
it "should change the key used" do
|
48
|
+
@hash[:testing] = "key change"
|
49
|
+
@hash.key = "some_new_key"
|
50
|
+
@hash.key.should == "some_new_key"
|
51
|
+
end
|
52
|
+
it "should move all hash data to a new key" do
|
53
|
+
@hash[:testing] = "hash migration"
|
54
|
+
@hash.key = "some_new_key"
|
55
|
+
@hash[:foo].should == "bar"
|
56
|
+
end
|
57
|
+
it "should not whine when the hash is empty" do
|
58
|
+
hash = Redis::BigHash.new :frequent => :plyer
|
59
|
+
hash.key = :flyer
|
60
|
+
hash.key.should == :flyer
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
describe "#keys" do
|
65
|
+
it "should return a list of all keys" do
|
66
|
+
@hash.keys.should == ["foo", "yin"]
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
describe "#key?" do
|
71
|
+
it "should return true when a key is present" do
|
72
|
+
@hash.key?(:foo).should be_true
|
73
|
+
end
|
74
|
+
it "should return false when a key is not present" do
|
75
|
+
@hash.key?(:fubar).should be_false
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
describe "#update" do
|
80
|
+
it "should update BigHash with values from another hash" do
|
81
|
+
@hash.update :test1 => "value1", :test2 => "value2"
|
82
|
+
@hash[:test1].should == "value1"
|
83
|
+
@hash[:test2].should == "value2"
|
84
|
+
end
|
85
|
+
it "should allow values to be arbitrary ruby objects" do
|
86
|
+
t = Time.now; r = Rational(22,7)
|
87
|
+
@hash.update :test1 => t, :test2 => r
|
88
|
+
@hash[:test1].should == t
|
89
|
+
@hash[:test2].to_s.should == "22/7"
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
describe "#delete" do
|
94
|
+
it "should return the current value" do
|
95
|
+
@hash.delete(:foo).should == "bar"
|
96
|
+
end
|
97
|
+
it "should remove the value" do
|
98
|
+
@hash.delete(:foo)
|
99
|
+
@hash[:foo].should be_nil
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
describe "#namespace" do
|
104
|
+
it "should prepend the namespace onto the key" do
|
105
|
+
@hash.namespace = "test_namespace"
|
106
|
+
@hash.redis_key.should =~ /^test_namespace:/
|
107
|
+
end
|
108
|
+
it "should migrate existing values over" do
|
109
|
+
@hash.namespace = "test_namespace"
|
110
|
+
@hash[:foo].should == "bar"
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
after :each do
|
115
|
+
@hash.destroy
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
@@ -0,0 +1,160 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
# MOST OF THIS COPIED FROM native_hash_spec !!!!
|
4
|
+
# Redis::LazyHash should behave pretty much the same as Redis::LazyHash
|
5
|
+
# only, lazier...
|
6
|
+
|
7
|
+
describe Redis::LazyHash do
|
8
|
+
before :each do
|
9
|
+
@hash = Redis::LazyHash.new :test
|
10
|
+
@hash.update("foo" => "bar")
|
11
|
+
@hash.save
|
12
|
+
@hash = Redis::LazyHash.new :test => @hash.key
|
13
|
+
end
|
14
|
+
|
15
|
+
describe "#loaded?" do
|
16
|
+
it "should not be loaded when no read/write has occurred" do
|
17
|
+
@hash.loaded?.should be_false
|
18
|
+
end
|
19
|
+
it "should be loaded after a read occurs" do
|
20
|
+
@hash[:foo]
|
21
|
+
@hash.loaded?.should be_true
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe "#save" do
|
26
|
+
it "should presist changes to existing hash key" do
|
27
|
+
@hash["foo"] = "something else"
|
28
|
+
@hash.save
|
29
|
+
hash = Redis::LazyHash.find :test => @hash.key
|
30
|
+
hash["foo"].should == "something else"
|
31
|
+
end
|
32
|
+
it "should persist new hash keys" do
|
33
|
+
@hash["yin"] = "yang"
|
34
|
+
@hash.save
|
35
|
+
hash = Redis::LazyHash.find :test => @hash.key
|
36
|
+
hash["yin"].should == "yang"
|
37
|
+
end
|
38
|
+
it "should remove deleted keys from redis" do
|
39
|
+
@hash["yin"] = "yang"
|
40
|
+
@hash.delete("foo")
|
41
|
+
@hash["foo"].should == nil
|
42
|
+
@hash.save
|
43
|
+
hash = Redis::LazyHash.find :test => @hash.key
|
44
|
+
hash["foo"].should == nil
|
45
|
+
end
|
46
|
+
it "should respect changes made since last read from redis" do
|
47
|
+
@hash.inspect # have to touch the hash first
|
48
|
+
concurrent_edit = Redis::LazyHash.find :test => @hash.key
|
49
|
+
concurrent_edit["foo"] = "race value"
|
50
|
+
concurrent_edit.save
|
51
|
+
@hash["yin"] = "yang"
|
52
|
+
@hash["foo"] = "bad value"
|
53
|
+
@hash.save
|
54
|
+
hash = Redis::LazyHash.find :test => @hash.key
|
55
|
+
hash["foo"].should == "race value"
|
56
|
+
hash["yin"].should == "yang"
|
57
|
+
end
|
58
|
+
it "should respect removed hash keys since last read" do
|
59
|
+
@hash.inspect
|
60
|
+
concurrent_edit = Redis::LazyHash.find :test => @hash.key
|
61
|
+
concurrent_edit["yin"] = "yang"
|
62
|
+
concurrent_edit.delete("foo")
|
63
|
+
concurrent_edit.save
|
64
|
+
@hash["foo"] = "bad value"
|
65
|
+
@hash.save
|
66
|
+
hash = Redis::LazyHash.find :test => @hash.key
|
67
|
+
hash["foo"].should == nil
|
68
|
+
hash["yin"].should == "yang"
|
69
|
+
end
|
70
|
+
it "should allow overwrite of concurrent edit after #reload! is called" do
|
71
|
+
@hash.inspect
|
72
|
+
concurrent_edit = Redis::LazyHash.find :test => @hash.key
|
73
|
+
concurrent_edit["yin"] = "yang"
|
74
|
+
concurrent_edit.delete("foo")
|
75
|
+
concurrent_edit.save
|
76
|
+
@hash.reload!
|
77
|
+
@hash["foo"].should == nil
|
78
|
+
@hash["foo"] = "good value"
|
79
|
+
@hash.save
|
80
|
+
hash = Redis::LazyHash.find :test => @hash.key
|
81
|
+
hash["foo"].should == "good value"
|
82
|
+
end
|
83
|
+
it "should treat string and symbolic keys the same" do
|
84
|
+
@hash[:foo].should == "bar"
|
85
|
+
@hash[:test] = "good value"
|
86
|
+
@hash["test"].should == "good value"
|
87
|
+
@hash.save
|
88
|
+
hash = Redis::LazyHash.find :test => @hash.key
|
89
|
+
hash[:test].should == "good value"
|
90
|
+
hash["test"].should == "good value"
|
91
|
+
end
|
92
|
+
it "should properly store nested hashes" do
|
93
|
+
@hash[:test] = { :foo => :bar, :x => { :y => "z" } }
|
94
|
+
@hash[:test][:x][:y].should == "z"
|
95
|
+
@hash.save
|
96
|
+
hash = Redis::LazyHash.find :test => @hash.key
|
97
|
+
hash[:test][:foo].should == :bar
|
98
|
+
hash[:test][:x][:y].should == "z"
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
describe "#renew" do
|
103
|
+
it "should generate a new key" do
|
104
|
+
old_key = @hash.key
|
105
|
+
new_key = @hash.renew_key
|
106
|
+
@hash.key.should eq(new_key)
|
107
|
+
@hash.key.should_not eq(old_key)
|
108
|
+
end
|
109
|
+
it "should remove the old hash from redis" do
|
110
|
+
old_key = @hash.key
|
111
|
+
namespace = @hash.namespace
|
112
|
+
@hash.renew_key
|
113
|
+
hash = Redis::LazyHash.find namespace => old_key
|
114
|
+
hash.inspect
|
115
|
+
hash.size.should == 0
|
116
|
+
end
|
117
|
+
it "should not persist the hash under the new key until #save is called" do
|
118
|
+
@hash["good key"] = "good value"
|
119
|
+
key = @hash.renew_key
|
120
|
+
bad_hash = Redis::LazyHash.find :test => key
|
121
|
+
bad_hash.size.should == 0
|
122
|
+
@hash.save
|
123
|
+
good_hash = Redis::LazyHash.find :test => key
|
124
|
+
good_hash["good key"].should eq("good value")
|
125
|
+
good_hash["foo"].should eq("bar")
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
describe "#key=" do
|
130
|
+
it "should allow an arbitrary key to be used" do
|
131
|
+
@hash.key = "blah@blah.com"
|
132
|
+
@hash.save
|
133
|
+
a_hash = Redis::LazyHash.find :test => "blah@blah.com"
|
134
|
+
a_hash["foo"].should eq('bar')
|
135
|
+
end
|
136
|
+
it "should not leave the old hash behind when the key is changed" do
|
137
|
+
old_key = @hash.key
|
138
|
+
@hash.key = "carl@linkleaf.com"
|
139
|
+
@hash.save
|
140
|
+
bad_hash = Redis::LazyHash.find :test => old_key
|
141
|
+
bad_hash.size.should == 0
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
describe ".find" do
|
146
|
+
it "should find an existing redis hash" do
|
147
|
+
hash = Redis::LazyHash.find :test => @hash.key
|
148
|
+
hash["foo"].should == "bar"
|
149
|
+
end
|
150
|
+
it "should return an empty hash when hash not found" do
|
151
|
+
hash = Redis::LazyHash.find :foo => :doesnt_exist
|
152
|
+
hash.size.should == 0
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
|
157
|
+
after :each do
|
158
|
+
@hash.destroy
|
159
|
+
end
|
160
|
+
end
|