instructure-redis-store 1.0.0.1.instructure1
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/.travis.yml +7 -0
- data/CHANGELOG +311 -0
- data/Gemfile +34 -0
- data/MIT-LICENSE +20 -0
- data/README.md +239 -0
- data/Rakefile +60 -0
- data/VERSION +1 -0
- data/lib/action_controller/session/redis_session_store.rb +81 -0
- data/lib/active_support/cache/redis_store.rb +254 -0
- data/lib/cache/merb/redis_store.rb +79 -0
- data/lib/cache/sinatra/redis_store.rb +131 -0
- data/lib/i18n/backend/redis.rb +67 -0
- data/lib/rack/cache/redis_entitystore.rb +48 -0
- data/lib/rack/cache/redis_metastore.rb +40 -0
- data/lib/rack/session/merb.rb +32 -0
- data/lib/rack/session/redis.rb +88 -0
- data/lib/redis-store.rb +45 -0
- data/lib/redis/distributed_store.rb +39 -0
- data/lib/redis/factory.rb +46 -0
- data/lib/redis/store.rb +39 -0
- data/lib/redis/store/interface.rb +17 -0
- data/lib/redis/store/marshalling.rb +51 -0
- data/lib/redis/store/namespace.rb +62 -0
- data/lib/redis/store/ttl.rb +37 -0
- data/lib/redis/store/version.rb +12 -0
- data/spec/action_controller/session/redis_session_store_spec.rb +126 -0
- data/spec/active_support/cache/redis_store_spec.rb +426 -0
- data/spec/cache/merb/redis_store_spec.rb +143 -0
- data/spec/cache/sinatra/redis_store_spec.rb +192 -0
- data/spec/config/node-one.conf +417 -0
- data/spec/config/node-two.conf +417 -0
- data/spec/config/redis.conf +417 -0
- data/spec/i18n/backend/redis_spec.rb +72 -0
- data/spec/rack/cache/entitystore/pony.jpg +0 -0
- data/spec/rack/cache/entitystore/redis_spec.rb +124 -0
- data/spec/rack/cache/metastore/redis_spec.rb +259 -0
- data/spec/rack/session/redis_spec.rb +234 -0
- data/spec/redis/distributed_store_spec.rb +55 -0
- data/spec/redis/factory_spec.rb +110 -0
- data/spec/redis/store/interface_spec.rb +23 -0
- data/spec/redis/store/marshalling_spec.rb +119 -0
- data/spec/redis/store/namespace_spec.rb +76 -0
- data/spec/redis/store/version_spec.rb +7 -0
- data/spec/redis/store_spec.rb +13 -0
- data/spec/spec_helper.rb +43 -0
- data/tasks/redis.tasks.rb +235 -0
- metadata +249 -0
@@ -0,0 +1,39 @@
|
|
1
|
+
class Redis
|
2
|
+
class DistributedStore < Distributed
|
3
|
+
attr_reader :ring
|
4
|
+
|
5
|
+
def initialize(addresses, options = { })
|
6
|
+
nodes = addresses.map do |address|
|
7
|
+
::Redis::Store.new address
|
8
|
+
end
|
9
|
+
_extend_namespace options
|
10
|
+
@ring = Redis::HashRing.new nodes
|
11
|
+
end
|
12
|
+
|
13
|
+
def nodes
|
14
|
+
ring.nodes
|
15
|
+
end
|
16
|
+
|
17
|
+
def reconnect
|
18
|
+
nodes.each {|node| node.reconnect }
|
19
|
+
end
|
20
|
+
|
21
|
+
def set(key, value, options = nil)
|
22
|
+
node_for(key).set(key, value, options)
|
23
|
+
end
|
24
|
+
|
25
|
+
def get(key, options = nil)
|
26
|
+
node_for(key).get(key, options)
|
27
|
+
end
|
28
|
+
|
29
|
+
def setnx(key, value, options = nil)
|
30
|
+
node_for(key).setnx(key, value, options)
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
def _extend_namespace(options)
|
35
|
+
@namespace = options[:namespace]
|
36
|
+
extend ::Redis::Store::Namespace if @namespace
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
class Redis
|
2
|
+
class Factory
|
3
|
+
def self.create(*redis_client_options)
|
4
|
+
redis_client_options = redis_client_options.flatten.compact.inject([]) do |result, address|
|
5
|
+
result << convert_to_redis_client_options(address)
|
6
|
+
result
|
7
|
+
end
|
8
|
+
if redis_client_options.size > 1
|
9
|
+
::Redis::DistributedStore.new redis_client_options
|
10
|
+
else
|
11
|
+
::Redis::Store.new redis_client_options.first || {}
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.convert_to_redis_client_options(address_or_options)
|
16
|
+
if address_or_options.is_a?(Hash)
|
17
|
+
options = address_or_options.dup
|
18
|
+
options[:namespace] ||= options.delete(:key_prefix) # RailsSessionStore
|
19
|
+
options
|
20
|
+
else
|
21
|
+
if address_or_options =~ /redis\:\/\//
|
22
|
+
require 'uri'
|
23
|
+
uri = URI.parse address_or_options
|
24
|
+
_, db, namespace = if uri.path
|
25
|
+
uri.path.split /\//
|
26
|
+
end
|
27
|
+
else
|
28
|
+
warn "[DEPRECATION] `#{address_or_options}` is deprecated. Please use `redis://#{address_or_options}` instead."
|
29
|
+
address_or_options, password = address_or_options.split(/\@/).reverse
|
30
|
+
password = password.gsub(/\:/, "") if password
|
31
|
+
host, port = address_or_options.split /\:/
|
32
|
+
port, db, namespace = port.split /\// if port
|
33
|
+
end
|
34
|
+
|
35
|
+
options = {}
|
36
|
+
options[:host] = host || uri && uri.host
|
37
|
+
options[:port] = port || uri && uri.port
|
38
|
+
options[:db] = db.to_i if db
|
39
|
+
options[:namespace] = namespace if namespace
|
40
|
+
options[:password] = password || uri && uri.password
|
41
|
+
[:host, :port, :password].each { |o| options.delete(o) if options[o].nil? }
|
42
|
+
options
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
data/lib/redis/store.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
class Redis
|
2
|
+
class Store < self
|
3
|
+
include Ttl, Interface
|
4
|
+
|
5
|
+
def initialize(options = { })
|
6
|
+
super
|
7
|
+
_extend_marshalling options
|
8
|
+
_extend_namespace options
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.rails3? #:nodoc:
|
12
|
+
defined?(::Rails) && ::Rails::VERSION::MAJOR == 3
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.rails31? #:nodoc:
|
16
|
+
defined?(::Rails) && ::Rails::VERSION::MAJOR == 3 && ::Rails::VERSION::MINOR == 1
|
17
|
+
end
|
18
|
+
|
19
|
+
def reconnect
|
20
|
+
@client.reconnect
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_s
|
24
|
+
"Redis Client connected to #{@client.host}:#{@client.port} against DB #{@client.db}"
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
def _extend_marshalling(options)
|
29
|
+
@marshalling = !(options[:marshalling] === false) # HACK - TODO delegate to Factory
|
30
|
+
extend Marshalling if @marshalling
|
31
|
+
end
|
32
|
+
|
33
|
+
def _extend_namespace(options)
|
34
|
+
@namespace = options[:namespace]
|
35
|
+
extend Namespace if @namespace
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class Redis
|
2
|
+
class Store < self
|
3
|
+
module Interface
|
4
|
+
def get(key, options = nil)
|
5
|
+
super(key)
|
6
|
+
end
|
7
|
+
|
8
|
+
def set(key, value, options = nil)
|
9
|
+
super(key, value)
|
10
|
+
end
|
11
|
+
|
12
|
+
def setnx(key, value, options = nil)
|
13
|
+
super(key, value)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
class Redis
|
2
|
+
class Store < self
|
3
|
+
module Marshalling
|
4
|
+
def set(key, value, options = nil)
|
5
|
+
_marshal(value, options) { |value| super encode(key), encode(value), options }
|
6
|
+
end
|
7
|
+
|
8
|
+
def setnx(key, value, options = nil)
|
9
|
+
_marshal(value, options) { |value| super encode(key), encode(value), options }
|
10
|
+
end
|
11
|
+
|
12
|
+
def get(key, options = nil)
|
13
|
+
_unmarshal super(key), options
|
14
|
+
end
|
15
|
+
|
16
|
+
def mget(*keys)
|
17
|
+
options = keys.flatten.pop if keys.flatten.last.is_a?(Hash)
|
18
|
+
super(*keys).map do |result|
|
19
|
+
_unmarshal result, options
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
def _marshal(val, options)
|
25
|
+
yield marshal?(options) ? Marshal.dump(val) : val
|
26
|
+
end
|
27
|
+
|
28
|
+
def _unmarshal(val, options)
|
29
|
+
unmarshal?(val, options) ? Marshal.load(val) : val
|
30
|
+
end
|
31
|
+
|
32
|
+
def marshal?(options)
|
33
|
+
!(options && options[:raw])
|
34
|
+
end
|
35
|
+
|
36
|
+
def unmarshal?(result, options)
|
37
|
+
result && result.size > 0 && marshal?(options)
|
38
|
+
end
|
39
|
+
|
40
|
+
if defined?(Encoding)
|
41
|
+
def encode(string)
|
42
|
+
string.to_s.force_encoding(Encoding::BINARY)
|
43
|
+
end
|
44
|
+
else
|
45
|
+
def encode(string)
|
46
|
+
string
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
class Redis
|
2
|
+
class Store < self
|
3
|
+
module Namespace
|
4
|
+
def set(key, val, options = nil)
|
5
|
+
namespace(key) { |key| super(key, val, options) }
|
6
|
+
end
|
7
|
+
|
8
|
+
def setnx(key, val, options = nil)
|
9
|
+
namespace(key) { |key| super(key, val, options) }
|
10
|
+
end
|
11
|
+
|
12
|
+
def get(key, options = nil)
|
13
|
+
namespace(key) { |key| super(key, options) }
|
14
|
+
end
|
15
|
+
|
16
|
+
def exists(key)
|
17
|
+
namespace(key) { |key| super(key) }
|
18
|
+
end
|
19
|
+
|
20
|
+
def incrby(key, increment)
|
21
|
+
namespace(key) { |key| super(key, increment) }
|
22
|
+
end
|
23
|
+
|
24
|
+
def decrby(key, increment)
|
25
|
+
namespace(key) { |key| super(key, increment) }
|
26
|
+
end
|
27
|
+
|
28
|
+
def keys(pattern = "*")
|
29
|
+
namespace(pattern) { |pattern| super(pattern).map{|key| strip_namespace(key) } }
|
30
|
+
end
|
31
|
+
|
32
|
+
def del(*keys)
|
33
|
+
super *keys.map {|key| interpolate(key) }
|
34
|
+
end
|
35
|
+
|
36
|
+
def mget(*keys)
|
37
|
+
super *keys.map {|key| interpolate(key) }
|
38
|
+
end
|
39
|
+
|
40
|
+
def to_s
|
41
|
+
"#{super} with namespace #{@namespace}"
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
def namespace(key)
|
46
|
+
yield interpolate(key)
|
47
|
+
end
|
48
|
+
|
49
|
+
def interpolate(key)
|
50
|
+
key.match(namespace_regexp) ? key : "#{@namespace}:#{key}"
|
51
|
+
end
|
52
|
+
|
53
|
+
def strip_namespace(key)
|
54
|
+
key.gsub namespace_regexp, ""
|
55
|
+
end
|
56
|
+
|
57
|
+
def namespace_regexp
|
58
|
+
@namespace_regexp ||= %r{^#{@namespace}\:}
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
class Redis
|
2
|
+
class Store < self
|
3
|
+
module Ttl
|
4
|
+
def set(key, value, options = nil)
|
5
|
+
if ttl = expires_in(options)
|
6
|
+
setex(key, ttl, value)
|
7
|
+
else
|
8
|
+
super(key, value)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def setnx(key, value, options = nil)
|
13
|
+
if ttl = expires_in(options)
|
14
|
+
setnx_with_expire(key, value, ttl)
|
15
|
+
else
|
16
|
+
super(key, value)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
protected
|
21
|
+
def setnx_with_expire(key, value, ttl)
|
22
|
+
multi do
|
23
|
+
setnx(key, value)
|
24
|
+
expire(key, expires_in)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
def expires_in(options)
|
30
|
+
if options
|
31
|
+
# Rack::Session Merb Rails/Sinatra
|
32
|
+
options[:expire_after] || options[:expires_in] || options[:expire_in]
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,126 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
RAILS_SESSION_STORE_CLASS = ::Redis::Store.rails3? ? ActionDispatch::Session::RedisSessionStore : ActionController::Session::RedisSessionStore
|
3
|
+
|
4
|
+
describe RAILS_SESSION_STORE_CLASS do
|
5
|
+
attr_reader :app
|
6
|
+
|
7
|
+
before :each do
|
8
|
+
@app = Object.new
|
9
|
+
@store = RAILS_SESSION_STORE_CLASS.new(app)
|
10
|
+
@dstore = RAILS_SESSION_STORE_CLASS.new app, :servers => ["redis://127.0.0.1:6380/1", "redis://127.0.0.1:6381/1"]
|
11
|
+
@rabbit = OpenStruct.new :name => "bunny"
|
12
|
+
@white_rabbit = OpenStruct.new :color => "white"
|
13
|
+
@sid = "rabbit"
|
14
|
+
@env = {'rack.session.options' => {:id => @sid}}
|
15
|
+
with_store_management do |store|
|
16
|
+
class << store
|
17
|
+
attr_reader :pool
|
18
|
+
public :get_session, :set_session, :destroy
|
19
|
+
end
|
20
|
+
store.set_session(@env, @sid, @rabbit)
|
21
|
+
store.pool.del "counter"
|
22
|
+
store.pool.del "rub-a-dub"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should accept string connection params" do
|
27
|
+
redis = instantiate_store
|
28
|
+
redis.to_s.should == "Redis Client connected to localhost:6379 against DB 0"
|
29
|
+
|
30
|
+
redis = instantiate_store :servers => "redis://localhost"
|
31
|
+
redis.to_s.should == "Redis Client connected to localhost:6379 against DB 0"
|
32
|
+
|
33
|
+
redis = instantiate_store :servers => "redis://localhost:6380"
|
34
|
+
redis.to_s.should == "Redis Client connected to localhost:6380 against DB 0"
|
35
|
+
|
36
|
+
redis = instantiate_store :servers => "redis://localhost:6380/13"
|
37
|
+
redis.to_s.should == "Redis Client connected to localhost:6380 against DB 13"
|
38
|
+
|
39
|
+
redis = instantiate_store :servers => "redis://localhost:6380/13/theplaylist"
|
40
|
+
redis.to_s.should == "Redis Client connected to localhost:6380 against DB 13 with namespace theplaylist"
|
41
|
+
end
|
42
|
+
|
43
|
+
it "should accept hash connection params" do
|
44
|
+
redis = instantiate_store :servers => [{ :host => "192.168.0.1" }]
|
45
|
+
redis.to_s.should == "Redis Client connected to 192.168.0.1:6379 against DB 0"
|
46
|
+
|
47
|
+
redis = instantiate_store :servers => [{ :port => "6380" }]
|
48
|
+
redis.to_s.should == "Redis Client connected to 127.0.0.1:6380 against DB 0"
|
49
|
+
|
50
|
+
redis = instantiate_store :servers => [{ :db => 13 }]
|
51
|
+
redis.to_s.should == "Redis Client connected to 127.0.0.1:6379 against DB 13"
|
52
|
+
|
53
|
+
redis = instantiate_store :servers => [{ :key_prefix => "theplaylist" }]
|
54
|
+
redis.to_s.should == "Redis Client connected to 127.0.0.1:6379 against DB 0 with namespace theplaylist"
|
55
|
+
end
|
56
|
+
|
57
|
+
it "should accept options when :servers key isn't passed" do
|
58
|
+
redis = RAILS_SESSION_STORE_CLASS.new(app, :key_prefix => "theplaylist").instance_variable_get(:@pool)
|
59
|
+
redis.to_s.should == "Redis Client connected to localhost:6379 against DB 0 with namespace theplaylist"
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should instantiate a ring" do
|
63
|
+
store = instantiate_store
|
64
|
+
store.should be_kind_of(Redis::Store)
|
65
|
+
store = instantiate_store :servers => ["redis://127.0.0.1:6379/0", "redis://127.0.0.1:6379/1"]
|
66
|
+
store.should be_kind_of(Redis::DistributedStore)
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should read the data" do
|
70
|
+
with_store_management do |store|
|
71
|
+
store.get_session(@env, @sid).should === [@sid, @rabbit]
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
it "should write the data" do
|
76
|
+
with_store_management do |store|
|
77
|
+
store.set_session(@env, @sid, @white_rabbit)
|
78
|
+
store.get_session(@env, @sid).should === [@sid, @white_rabbit]
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
it "should delete the data" do
|
83
|
+
with_store_management do |store|
|
84
|
+
store.destroy(@env)
|
85
|
+
store.get_session(@env, @sid).should === [@sid, {}]
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
it "should write the data with expiration time" do
|
90
|
+
with_store_management do |store|
|
91
|
+
@env['rack.session.options'].merge!(:expires_in => 1.second)
|
92
|
+
store.set_session(@env, @sid, @white_rabbit)
|
93
|
+
store.get_session(@env, @sid).should === [@sid, @white_rabbit]; sleep 2
|
94
|
+
store.get_session(@env, @sid).should === [@sid, {}]
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
describe "namespace" do
|
99
|
+
before :each do
|
100
|
+
@namespace = "theplaylist"
|
101
|
+
@store = RAILS_SESSION_STORE_CLASS.new(lambda {|| }, :servers => [{ :namespace => @namespace }])
|
102
|
+
@pool = @store.instance_variable_get(:@pool)
|
103
|
+
@client = @pool.instance_variable_get(:@client)
|
104
|
+
end
|
105
|
+
|
106
|
+
it "should read the data" do
|
107
|
+
@client.should_receive(:call).with([:get, "#{@namespace}:#{@sid}"])
|
108
|
+
@store.send :get_session, @env, @sid
|
109
|
+
end
|
110
|
+
|
111
|
+
it "should write the data" do
|
112
|
+
@client.should_receive(:call).with([:set, "#{@namespace}:#{@sid}", Marshal.dump(@white_rabbit)])
|
113
|
+
@store.send :set_session, @env, @sid, @white_rabbit
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
private
|
118
|
+
def instantiate_store(params = { })
|
119
|
+
RAILS_SESSION_STORE_CLASS.new(app, params).instance_variable_get(:@pool)
|
120
|
+
end
|
121
|
+
|
122
|
+
def with_store_management
|
123
|
+
yield @store
|
124
|
+
yield @dstore
|
125
|
+
end
|
126
|
+
end
|
@@ -0,0 +1,426 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module ActiveSupport
|
4
|
+
module Cache
|
5
|
+
describe RedisStore do
|
6
|
+
before :each do
|
7
|
+
@store = ActiveSupport::Cache::RedisStore.new
|
8
|
+
@dstore = ActiveSupport::Cache::RedisStore.new "redis://127.0.0.1:6380/1", "redis://127.0.0.1:6381/1"
|
9
|
+
@rabbit = OpenStruct.new :name => "bunny"
|
10
|
+
@white_rabbit = OpenStruct.new :color => "white"
|
11
|
+
with_store_management do |store|
|
12
|
+
store.write "rabbit", @rabbit
|
13
|
+
store.delete "counter"
|
14
|
+
store.delete "rub-a-dub"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should accept connection params" do
|
19
|
+
redis = instantiate_store
|
20
|
+
redis.to_s.should == "Redis Client connected to 127.0.0.1:6379 against DB 0"
|
21
|
+
|
22
|
+
redis = instantiate_store "redis://127.0.0.1"
|
23
|
+
redis.to_s.should == "Redis Client connected to 127.0.0.1:6379 against DB 0"
|
24
|
+
|
25
|
+
redis = instantiate_store "redis://127.0.0.1:6380"
|
26
|
+
redis.to_s.should == "Redis Client connected to 127.0.0.1:6380 against DB 0"
|
27
|
+
|
28
|
+
redis = instantiate_store "redis://127.0.0.1:6380/13"
|
29
|
+
redis.to_s.should == "Redis Client connected to 127.0.0.1:6380 against DB 13"
|
30
|
+
|
31
|
+
redis = instantiate_store "redis://127.0.0.1:6380/13/theplaylist"
|
32
|
+
redis.to_s.should == "Redis Client connected to 127.0.0.1:6380 against DB 13 with namespace theplaylist"
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should instantiate a ring" do
|
36
|
+
store = instantiate_store
|
37
|
+
store.should be_kind_of(Redis::Store)
|
38
|
+
store = instantiate_store ["redis://127.0.0.1:6379/0", "redis://127.0.0.1:6379/1"]
|
39
|
+
store.should be_kind_of(Redis::DistributedStore)
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should force reconnection" do
|
43
|
+
data = @store.instance_variable_get(:@data)
|
44
|
+
data.should_receive(:reconnect)
|
45
|
+
@store.reconnect
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should read the data" do
|
49
|
+
with_store_management do |store|
|
50
|
+
store.read("rabbit").should === @rabbit
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should write the data" do
|
55
|
+
with_store_management do |store|
|
56
|
+
store.write "rabbit", @white_rabbit
|
57
|
+
store.read("rabbit").should === @white_rabbit
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
it "should write the data with expiration time" do
|
62
|
+
with_store_management do |store|
|
63
|
+
store.write "rabbit", @white_rabbit, :expires_in => 1.second
|
64
|
+
store.read("rabbit").should == @white_rabbit ; sleep 2
|
65
|
+
store.read("rabbit").should be_nil
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should not write data if :unless_exist option is true" do
|
70
|
+
with_store_management do |store|
|
71
|
+
store.write "rabbit", @white_rabbit, :unless_exist => true
|
72
|
+
store.read("rabbit").should == @rabbit
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
if ::Redis::Store.rails3?
|
77
|
+
if RUBY_VERSION.match /1\.9/
|
78
|
+
it "should read raw data" do
|
79
|
+
with_store_management do |store|
|
80
|
+
result = store.read("rabbit", :raw => true)
|
81
|
+
result.should include("ActiveSupport::Cache::Entry")
|
82
|
+
result.should include("\x0FOpenStruct{\x06:\tnameI\"\nbunny\x06:\x06EF")
|
83
|
+
end
|
84
|
+
end
|
85
|
+
else
|
86
|
+
it "should read raw data" do
|
87
|
+
with_store_management do |store|
|
88
|
+
result = store.read("rabbit", :raw => true)
|
89
|
+
result.should include("ActiveSupport::Cache::Entry")
|
90
|
+
result.should include("\017OpenStruct{\006:\tname\"\nbunny")
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
it "should write raw data" do
|
96
|
+
with_store_management do |store|
|
97
|
+
store.write "rabbit", @white_rabbit, :raw => true
|
98
|
+
store.read("rabbit", :raw => true).should include("ActiveSupport::Cache::Entry")
|
99
|
+
end
|
100
|
+
end
|
101
|
+
else
|
102
|
+
it "should read raw data" do
|
103
|
+
with_store_management do |store|
|
104
|
+
store.read("rabbit", :raw => true).should == Marshal.dump(@rabbit)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
it "should write raw data" do
|
109
|
+
with_store_management do |store|
|
110
|
+
store.write "rabbit", @white_rabbit, :raw => true
|
111
|
+
store.read("rabbit", :raw => true).should == %(#<OpenStruct color="white">)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
it "should delete data" do
|
117
|
+
with_store_management do |store|
|
118
|
+
store.delete "rabbit"
|
119
|
+
store.read("rabbit").should be_nil
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
it "should delete matched data" do
|
124
|
+
with_store_management do |store|
|
125
|
+
store.delete_matched "rabb*"
|
126
|
+
store.read("rabbit").should be_nil
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
it "should verify existence of an object in the store" do
|
131
|
+
with_store_management do |store|
|
132
|
+
store.exist?("rabbit").should be_true
|
133
|
+
store.exist?("rab-a-dub").should be_false
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
it "should increment a key" do
|
138
|
+
with_store_management do |store|
|
139
|
+
3.times { store.increment "counter" }
|
140
|
+
store.read("counter", :raw => true).to_i.should == 3
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
it "should decrement a key" do
|
145
|
+
with_store_management do |store|
|
146
|
+
3.times { store.increment "counter" }
|
147
|
+
2.times { store.decrement "counter" }
|
148
|
+
store.read("counter", :raw => true).to_i.should == 1
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
it "should increment a raw key" do
|
153
|
+
with_store_management do |store|
|
154
|
+
store.write("raw-counter", 1, :raw => true).should be_true
|
155
|
+
store.increment("raw-counter", 2)
|
156
|
+
store.read("raw-counter", :raw => true).to_i.should == 3
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
it "should decrement a raw key" do
|
161
|
+
with_store_management do |store|
|
162
|
+
store.write("raw-counter", 3, :raw => true).should be_true
|
163
|
+
store.decrement("raw-counter", 2)
|
164
|
+
store.read("raw-counter", :raw => true).to_i.should == 1
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
it "should increment a key by given value" do
|
169
|
+
with_store_management do |store|
|
170
|
+
store.increment "counter", 3
|
171
|
+
store.read("counter", :raw => true).to_i.should == 3
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
it "should decrement a key by given value" do
|
176
|
+
with_store_management do |store|
|
177
|
+
3.times { store.increment "counter" }
|
178
|
+
store.decrement "counter", 2
|
179
|
+
store.read("counter", :raw => true).to_i.should == 1
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
it "should clear the store" do
|
184
|
+
with_store_management do |store|
|
185
|
+
store.clear
|
186
|
+
store.instance_variable_get(:@data).keys("*").flatten.should be_empty
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
it "should return store stats" do
|
191
|
+
with_store_management do |store|
|
192
|
+
store.stats.should_not be_empty
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
it "should fetch data" do
|
197
|
+
with_store_management do |store|
|
198
|
+
store.fetch("rabbit").should == @rabbit
|
199
|
+
store.fetch("rub-a-dub").should be_nil
|
200
|
+
store.fetch("rub-a-dub") { "Flora de Cana" }
|
201
|
+
store.fetch("rub-a-dub").should === "Flora de Cana"
|
202
|
+
store.fetch("rabbit", :force => true) # force cache miss
|
203
|
+
store.fetch("rabbit", :force => true, :expires_in => 1.second) { @white_rabbit }
|
204
|
+
store.fetch("rabbit").should == @white_rabbit
|
205
|
+
sleep 2
|
206
|
+
store.fetch("rabbit").should be_nil
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
if ::Redis::Store.rails3?
|
211
|
+
it "should read multiple keys" do
|
212
|
+
@store.write "irish whisky", "Jameson"
|
213
|
+
result = @store.read_multi "rabbit", "irish whisky"
|
214
|
+
result['rabbit'].raw_value.should === @rabbit
|
215
|
+
result['irish whisky'].raw_value.should == "Jameson"
|
216
|
+
end
|
217
|
+
else
|
218
|
+
it "should read multiple keys" do
|
219
|
+
@store.write "irish whisky", "Jameson"
|
220
|
+
result = @store.read_multi "rabbit", "irish whisky"
|
221
|
+
result.should == { 'rabbit' => @rabbit, 'irish whisky' => 'Jameson' }
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
it 'should read multiple keys and return only matches' do
|
226
|
+
@store.delete 'irish whisky'
|
227
|
+
result = @store.read_multi "rabbit", "irish whisky"
|
228
|
+
result.should_not include('irish whisky')
|
229
|
+
result.should include('rabbit')
|
230
|
+
end
|
231
|
+
|
232
|
+
describe "namespace" do
|
233
|
+
before :each do
|
234
|
+
@namespace = "theplaylist"
|
235
|
+
@store = ActiveSupport::Cache::RedisStore.new :namespace => @namespace
|
236
|
+
@data = @store.instance_variable_get(:@data)
|
237
|
+
@client = @data.instance_variable_get(:@client)
|
238
|
+
end
|
239
|
+
|
240
|
+
it "should read the data" do
|
241
|
+
@client.should_receive(:call).with([:get, "#{@namespace}:rabbit"])
|
242
|
+
@store.read("rabbit")
|
243
|
+
end
|
244
|
+
|
245
|
+
if ::Redis::Store.rails3?
|
246
|
+
# it "should write the data"
|
247
|
+
# it "should write the data" do
|
248
|
+
# @data.should_receive(:set).with("#{@namespace}:rabbit"), Marshal.dump(ActiveSupport::Cache::Entry.new(@white_rabbit)))
|
249
|
+
# @store.write "rabbit", @white_rabbit
|
250
|
+
# end
|
251
|
+
else
|
252
|
+
it "should write the data" do
|
253
|
+
@client.should_receive(:call).with([:set, "#{@namespace}:rabbit", Marshal.dump(@white_rabbit)])
|
254
|
+
@store.write "rabbit", @white_rabbit
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
it "should delete the data" do
|
259
|
+
@client.should_receive(:call).with([:del, "#{@namespace}:rabbit"])
|
260
|
+
@store.delete "rabbit"
|
261
|
+
end
|
262
|
+
|
263
|
+
it "should delete matched data" do
|
264
|
+
@client.should_receive(:call).with([:del, "#{@namespace}:rabbit"])
|
265
|
+
@client.should_receive(:call).with([:keys, "theplaylist:rabb*"]).and_return [ "#{@namespace}:rabbit" ]
|
266
|
+
@store.delete_matched "rabb*"
|
267
|
+
end
|
268
|
+
|
269
|
+
if ::Redis::Store.rails3?
|
270
|
+
it "should verify existence of an object in the store" do
|
271
|
+
@client.should_receive(:call).with(:get, "#{@namespace}:rabbit")
|
272
|
+
@store.exist?("rabbit")
|
273
|
+
end
|
274
|
+
else
|
275
|
+
it "should verify existence of an object in the store" do
|
276
|
+
@client.should_receive(:call).with([:exists, "#{@namespace}:rabbit"])
|
277
|
+
@store.exist?("rabbit")
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
it "should increment a key" do
|
282
|
+
@client.should_receive(:call).with([:incrby, "#{@namespace}:counter", 1])
|
283
|
+
@store.increment "counter"
|
284
|
+
end
|
285
|
+
|
286
|
+
it "should decrement a key" do
|
287
|
+
@client.should_receive(:call).with([:decrby, "#{@namespace}:counter", 1])
|
288
|
+
@store.decrement "counter"
|
289
|
+
end
|
290
|
+
|
291
|
+
it "should fetch data" do
|
292
|
+
@client.should_receive(:call).with([:get, "#{@namespace}:rabbit"])
|
293
|
+
@store.fetch "rabbit"
|
294
|
+
end
|
295
|
+
|
296
|
+
it "should read multiple keys" do
|
297
|
+
rabbits = [ Marshal.dump(@rabbit), Marshal.dump(@white_rabbit) ]
|
298
|
+
@client.should_receive(:call).with([:mget, "#{@namespace}:rabbit", "#{@namespace}:white_rabbit"]).and_return rabbits
|
299
|
+
@store.read_multi "rabbit", "white_rabbit"
|
300
|
+
end
|
301
|
+
end
|
302
|
+
|
303
|
+
if ::Redis::Store.rails3?
|
304
|
+
describe "notifications" do
|
305
|
+
it "should notify on #fetch" do
|
306
|
+
with_notifications do
|
307
|
+
@store.fetch("radiohead") { "House Of Cards" }
|
308
|
+
end
|
309
|
+
|
310
|
+
read, generate, write = @events
|
311
|
+
read.name.should == "cache_read.active_support"
|
312
|
+
read.payload.should == { :key => "radiohead", :super_operation => :fetch }
|
313
|
+
generate.name.should == "cache_generate.active_support"
|
314
|
+
generate.payload.should == { :key => "radiohead" }
|
315
|
+
write.name.should == "cache_write.active_support"
|
316
|
+
write.payload.should == { :key => "radiohead" }
|
317
|
+
end
|
318
|
+
|
319
|
+
it "should notify on #read" do
|
320
|
+
with_notifications do
|
321
|
+
@store.read "metallica"
|
322
|
+
end
|
323
|
+
|
324
|
+
read = @events.first
|
325
|
+
read.name.should == "cache_read.active_support"
|
326
|
+
read.payload.should == { :key => "metallica", :hit => false }
|
327
|
+
end
|
328
|
+
|
329
|
+
# it "should notify on #read_multi" # Not supported in Rails 3
|
330
|
+
|
331
|
+
it "should notify on #write" do
|
332
|
+
with_notifications do
|
333
|
+
@store.write "depeche mode", "Enjoy The Silence"
|
334
|
+
end
|
335
|
+
|
336
|
+
write = @events.first
|
337
|
+
write.name.should == "cache_write.active_support"
|
338
|
+
write.payload.should == { :key => "depeche mode" }
|
339
|
+
end
|
340
|
+
|
341
|
+
it "should notify on #delete" do
|
342
|
+
with_notifications do
|
343
|
+
@store.delete "the new cardigans"
|
344
|
+
end
|
345
|
+
|
346
|
+
delete = @events.first
|
347
|
+
delete.name.should == "cache_delete.active_support"
|
348
|
+
delete.payload.should == { :key => "the new cardigans" }
|
349
|
+
end
|
350
|
+
|
351
|
+
it "should notify on #exist?" do
|
352
|
+
with_notifications do
|
353
|
+
@store.exist? "the smiths"
|
354
|
+
end
|
355
|
+
|
356
|
+
exist = @events.first
|
357
|
+
exist.name.should == "cache_exist?.active_support"
|
358
|
+
exist.payload.should == { :key => "the smiths" }
|
359
|
+
end
|
360
|
+
|
361
|
+
it "should notify on #delete_matched" do
|
362
|
+
with_notifications do
|
363
|
+
@store.delete_matched "afterhours*"
|
364
|
+
end
|
365
|
+
|
366
|
+
delete_matched = @events.first
|
367
|
+
delete_matched.name.should == "cache_delete_matched.active_support"
|
368
|
+
delete_matched.payload.should == { :key => %("afterhours*") }
|
369
|
+
end
|
370
|
+
|
371
|
+
it "should notify on #increment" do
|
372
|
+
with_notifications do
|
373
|
+
@store.increment "pearl jam"
|
374
|
+
end
|
375
|
+
|
376
|
+
increment = @events.first
|
377
|
+
increment.name.should == "cache_increment.active_support"
|
378
|
+
increment.payload.should == { :key => "pearl jam", :amount => 1 }
|
379
|
+
end
|
380
|
+
|
381
|
+
it "should notify on #decrement" do
|
382
|
+
with_notifications do
|
383
|
+
@store.decrement "placebo"
|
384
|
+
end
|
385
|
+
|
386
|
+
decrement = @events.first
|
387
|
+
decrement.name.should == "cache_decrement.active_support"
|
388
|
+
decrement.payload.should == { :key => "placebo", :amount => 1 }
|
389
|
+
end
|
390
|
+
|
391
|
+
# it "should notify on cleanup" # TODO implement in ActiveSupport::Cache::RedisStore
|
392
|
+
|
393
|
+
it "should notify on clear" do
|
394
|
+
with_notifications do
|
395
|
+
@store.clear
|
396
|
+
end
|
397
|
+
|
398
|
+
clear = @events.first
|
399
|
+
clear.name.should == "cache_clear.active_support"
|
400
|
+
clear.payload.should == { :key => nil }
|
401
|
+
end
|
402
|
+
end
|
403
|
+
end
|
404
|
+
|
405
|
+
private
|
406
|
+
def instantiate_store(addresses = nil)
|
407
|
+
ActiveSupport::Cache::RedisStore.new(addresses).instance_variable_get(:@data)
|
408
|
+
end
|
409
|
+
|
410
|
+
def with_store_management
|
411
|
+
yield @store
|
412
|
+
yield @dstore
|
413
|
+
end
|
414
|
+
|
415
|
+
def with_notifications
|
416
|
+
@events = [ ]
|
417
|
+
ActiveSupport::Cache::RedisStore.instrument = true
|
418
|
+
ActiveSupport::Notifications.subscribe(/^cache_(.*)\.active_support$/) do |*args|
|
419
|
+
@events << ActiveSupport::Notifications::Event.new(*args)
|
420
|
+
end
|
421
|
+
yield
|
422
|
+
ActiveSupport::Cache::RedisStore.instrument = false
|
423
|
+
end
|
424
|
+
end
|
425
|
+
end
|
426
|
+
end
|