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.
Files changed (47) hide show
  1. data/.travis.yml +7 -0
  2. data/CHANGELOG +311 -0
  3. data/Gemfile +34 -0
  4. data/MIT-LICENSE +20 -0
  5. data/README.md +239 -0
  6. data/Rakefile +60 -0
  7. data/VERSION +1 -0
  8. data/lib/action_controller/session/redis_session_store.rb +81 -0
  9. data/lib/active_support/cache/redis_store.rb +254 -0
  10. data/lib/cache/merb/redis_store.rb +79 -0
  11. data/lib/cache/sinatra/redis_store.rb +131 -0
  12. data/lib/i18n/backend/redis.rb +67 -0
  13. data/lib/rack/cache/redis_entitystore.rb +48 -0
  14. data/lib/rack/cache/redis_metastore.rb +40 -0
  15. data/lib/rack/session/merb.rb +32 -0
  16. data/lib/rack/session/redis.rb +88 -0
  17. data/lib/redis-store.rb +45 -0
  18. data/lib/redis/distributed_store.rb +39 -0
  19. data/lib/redis/factory.rb +46 -0
  20. data/lib/redis/store.rb +39 -0
  21. data/lib/redis/store/interface.rb +17 -0
  22. data/lib/redis/store/marshalling.rb +51 -0
  23. data/lib/redis/store/namespace.rb +62 -0
  24. data/lib/redis/store/ttl.rb +37 -0
  25. data/lib/redis/store/version.rb +12 -0
  26. data/spec/action_controller/session/redis_session_store_spec.rb +126 -0
  27. data/spec/active_support/cache/redis_store_spec.rb +426 -0
  28. data/spec/cache/merb/redis_store_spec.rb +143 -0
  29. data/spec/cache/sinatra/redis_store_spec.rb +192 -0
  30. data/spec/config/node-one.conf +417 -0
  31. data/spec/config/node-two.conf +417 -0
  32. data/spec/config/redis.conf +417 -0
  33. data/spec/i18n/backend/redis_spec.rb +72 -0
  34. data/spec/rack/cache/entitystore/pony.jpg +0 -0
  35. data/spec/rack/cache/entitystore/redis_spec.rb +124 -0
  36. data/spec/rack/cache/metastore/redis_spec.rb +259 -0
  37. data/spec/rack/session/redis_spec.rb +234 -0
  38. data/spec/redis/distributed_store_spec.rb +55 -0
  39. data/spec/redis/factory_spec.rb +110 -0
  40. data/spec/redis/store/interface_spec.rb +23 -0
  41. data/spec/redis/store/marshalling_spec.rb +119 -0
  42. data/spec/redis/store/namespace_spec.rb +76 -0
  43. data/spec/redis/store/version_spec.rb +7 -0
  44. data/spec/redis/store_spec.rb +13 -0
  45. data/spec/spec_helper.rb +43 -0
  46. data/tasks/redis.tasks.rb +235 -0
  47. 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
@@ -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,12 @@
1
+ class Redis
2
+ class Store < self
3
+ module VERSION #:nodoc:
4
+ MAJOR = 1
5
+ MINOR = 0
6
+ TINY = 0
7
+ BUILD = 1
8
+
9
+ STRING = [MAJOR, MINOR, TINY, BUILD].join('.')
10
+ end
11
+ end
12
+ 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