instructure-redis-store 1.0.0.1.instructure1

Sign up to get free protection for your applications and to get access to all the features.
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
data/Rakefile ADDED
@@ -0,0 +1,60 @@
1
+ $:.unshift 'lib'
2
+ require 'rubygems'
3
+ require 'rake'
4
+ require 'rake/testtask'
5
+ require 'rake/rdoctask'
6
+ require 'spec/rake/spectask'
7
+
8
+ task :default => "spec:suite"
9
+
10
+ begin
11
+ require "jeweler"
12
+ Jeweler::Tasks.new do |gemspec|
13
+ gemspec.name = "instructure-redis-store"
14
+ gemspec.summary = "Namespaced Rack::Session, Rack::Cache, I18n and cache Redis stores for Ruby web frameworks."
15
+ gemspec.description = "Namespaced Rack::Session, Rack::Cache, I18n and cache Redis stores for Ruby web frameworks."
16
+ gemspec.email = "brianp@instructure.com"
17
+ gemspec.homepage = "http://github.com/instructure/redis-store"
18
+ gemspec.authors = [ "Luca Guidi", "Brian Palmer" ]
19
+ gemspec.executables = [ ]
20
+ gemspec.add_dependency "redis", "3.0.1"
21
+ end
22
+
23
+ Jeweler::GemcutterTasks.new
24
+ rescue LoadError
25
+ puts "Jeweler not available. Install it with: sudo gem install jeweler"
26
+ end
27
+
28
+ namespace :spec do
29
+ desc "Run all the examples by starting a detached Redis instance"
30
+ task :suite => :prepare do
31
+ invoke_with_redis_replication "spec:run"
32
+ end
33
+
34
+ Spec::Rake::SpecTask.new(:run) do |t|
35
+ t.spec_files = FileList['spec/**/*_spec.rb']
36
+ t.spec_opts = %w(-fs --color)
37
+ end
38
+ end
39
+
40
+ desc "Run all examples with RCov"
41
+ task :rcov => :prepare do
42
+ invoke_with_redis_replication "rcov_run"
43
+ end
44
+
45
+ Spec::Rake::SpecTask.new(:rcov_run) do |t|
46
+ t.spec_files = FileList['spec/**/*_spec.rb']
47
+ t.rcov = true
48
+ end
49
+
50
+ task :prepare do
51
+ `mkdir -p tmp && rm tmp/*.rdb`
52
+ end
53
+
54
+ namespace :bundle do
55
+ task :clean do
56
+ system "rm -rf ~/.bundle/ ~/.gem/ .bundle/ Gemfile.lock"
57
+ end
58
+ end
59
+
60
+ load "tasks/redis.tasks.rb"
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.0.0.1.instructure1
@@ -0,0 +1,81 @@
1
+ require "redis-store"
2
+
3
+ module RedisStore
4
+ module Rack
5
+ module Session
6
+ # Redis session storage for Rails, and for Rails only. Derived from
7
+ # the MemCacheStore code, simply dropping in Redis instead.
8
+ #
9
+ # Options:
10
+ # :key => Same as with the other cookie stores, key name
11
+ # :secret => Encryption secret for the key
12
+ # :host => Redis host name, default is localhost
13
+ # :port => Redis port, default is 6379
14
+ # :db => Database number, defaults to 0. Useful to separate your session storage from other data
15
+ # :key_prefix => Prefix for keys used in Redis, e.g. myapp-. Useful to separate session storage keys visibly from others
16
+ # :expire_after => A number in seconds to set the timeout interval for the session. Will map directly to expiry in Redis
17
+ module Rails
18
+ def initialize(app, options = {})
19
+ # Support old :expires option
20
+ options[:expire_after] ||= options[:expires]
21
+
22
+ super
23
+
24
+ options = options.dup
25
+ servers = [ options.delete(:servers) ].flatten.compact
26
+ servers = [ "redis://localhost:6379/0" ] if servers.empty?
27
+ servers.map! do |server|
28
+ server = Redis::Factory.convert_to_redis_client_options(server)
29
+ server.merge(options)
30
+ end
31
+
32
+ @pool = Redis::Factory.create(*servers)
33
+ end
34
+
35
+ private
36
+ def get_session(env, sid)
37
+ sid ||= generate_sid
38
+ begin
39
+ session = @pool.get(sid) || {}
40
+ rescue Errno::ECONNREFUSED
41
+ session = {}
42
+ end
43
+ [sid, session]
44
+ end
45
+
46
+ def set_session(env, sid, session_data, opts=nil)
47
+ options = env['rack.session.options']
48
+ @pool.set(sid, session_data, options)
49
+ return(::Redis::Store.rails3? ? sid : true)
50
+ rescue Errno::ECONNREFUSED
51
+ return false
52
+ end
53
+
54
+ def destroy(env)
55
+ if sid = current_session_id(env)
56
+ @pool.del(sid)
57
+ sid
58
+ end
59
+ rescue Errno::ECONNREFUSED
60
+ false
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+ if ::Redis::Store.rails31?
67
+ require 'action_dispatch/middleware/session/abstract_store'
68
+ class ActionDispatch::Session::RedisSessionStore < Rack::Session::Redis
69
+ include ActionDispatch::Session::Compatibility
70
+ include ActionDispatch::Session::StaleSessionCheck
71
+ end
72
+ elsif ::Redis::Store.rails3?
73
+ class ActionDispatch::Session::RedisSessionStore < ActionDispatch::Session::AbstractStore
74
+ include RedisStore::Rack::Session::Rails
75
+ end
76
+ else
77
+ class ActionController::Session::RedisSessionStore < ActionController::Session::AbstractStore
78
+ include RedisStore::Rack::Session::Rails
79
+ end
80
+ end
81
+
@@ -0,0 +1,254 @@
1
+ require "redis-store"
2
+
3
+ module ::RedisStore
4
+ module Cache
5
+ module Rails2
6
+ # Instantiate the store.
7
+ #
8
+ # Example:
9
+ # RedisStore.new
10
+ # # => host: localhost, port: 6379, db: 0
11
+ #
12
+ # RedisStore.new "example.com"
13
+ # # => host: example.com, port: 6379, db: 0
14
+ #
15
+ # RedisStore.new "example.com:23682"
16
+ # # => host: example.com, port: 23682, db: 0
17
+ #
18
+ # RedisStore.new "example.com:23682/1"
19
+ # # => host: example.com, port: 23682, db: 1
20
+ #
21
+ # RedisStore.new "example.com:23682/1/theplaylist"
22
+ # # => host: example.com, port: 23682, db: 1, namespace: theplaylist
23
+ #
24
+ # RedisStore.new "localhost:6379/0", "localhost:6380/0"
25
+ # # => instantiate a cluster
26
+ def initialize(*addresses)
27
+ @data = ::Redis::Factory.create(addresses)
28
+ end
29
+
30
+ def write(key, value, options = nil)
31
+ super
32
+ method = options && options[:unless_exist] ? :setnx : :set
33
+ @data.send method, key, value, options
34
+ end
35
+
36
+ def read(key, options = nil)
37
+ super
38
+ @data.get key, options
39
+ end
40
+
41
+ def delete(key, options = nil)
42
+ super
43
+ @data.del key
44
+ end
45
+
46
+ def exist?(key, options = nil)
47
+ super
48
+ @data.exists key
49
+ end
50
+
51
+ # Delete objects for matched keys.
52
+ #
53
+ # Example:
54
+ # cache.del_matched "rab*"
55
+ def delete_matched(matcher, options = nil)
56
+ instrument(:delete_matched, matcher, options) do
57
+ !(keys = @data.keys(matcher)).empty? && @data.del(*keys)
58
+ end
59
+ end
60
+
61
+ private
62
+ def instrument(operation, key, options = nil)
63
+ log(operation.to_s, key, options)
64
+ yield
65
+ end
66
+ end
67
+
68
+ module Rails3
69
+ # Instantiate the store.
70
+ #
71
+ # Example:
72
+ # RedisStore.new
73
+ # # => host: localhost, port: 6379, db: 0
74
+ #
75
+ # RedisStore.new "example.com"
76
+ # # => host: example.com, port: 6379, db: 0
77
+ #
78
+ # RedisStore.new "example.com:23682"
79
+ # # => host: example.com, port: 23682, db: 0
80
+ #
81
+ # RedisStore.new "example.com:23682/1"
82
+ # # => host: example.com, port: 23682, db: 1
83
+ #
84
+ # RedisStore.new "example.com:23682/1/theplaylist"
85
+ # # => host: example.com, port: 23682, db: 1, namespace: theplaylist
86
+ #
87
+ # RedisStore.new "localhost:6379/0", "localhost:6380/0"
88
+ # # => instantiate a cluster
89
+ def initialize(*addresses)
90
+ @data = ::Redis::Factory.create(addresses)
91
+ super(addresses.extract_options!)
92
+ end
93
+
94
+ # Delete objects for matched keys.
95
+ #
96
+ # Example:
97
+ # cache.del_matched "rab*"
98
+ def delete_matched(matcher, options = nil)
99
+ options = merged_options(options)
100
+ instrument(:delete_matched, matcher.inspect) do
101
+ matcher = key_matcher(matcher, options)
102
+ begin
103
+ !(keys = @data.keys(matcher)).empty? && @data.del(*keys)
104
+ rescue Errno::ECONNREFUSED => e
105
+ false
106
+ end
107
+ end
108
+ end
109
+
110
+ protected
111
+ def write_entry(key, entry, options)
112
+ method = options && options[:unless_exist] ? :setnx : :set
113
+ @data.send method, key, entry, options
114
+ rescue Errno::ECONNREFUSED => e
115
+ false
116
+ end
117
+
118
+ def read_entry(key, options)
119
+ entry = @data.get key, options
120
+ if entry
121
+ entry.is_a?(ActiveSupport::Cache::Entry) ? entry : ActiveSupport::Cache::Entry.new(entry)
122
+ end
123
+ rescue Errno::ECONNREFUSED => e
124
+ nil
125
+ end
126
+
127
+ ##
128
+ # Implement the ActiveSupport::Cache#delete_entry
129
+ #
130
+ # It's really needed and use
131
+ #
132
+ def delete_entry(key, options)
133
+ @data.del key
134
+ rescue Errno::ECONNREFUSED => e
135
+ false
136
+ end
137
+
138
+
139
+ # Add the namespace defined in the options to a pattern designed to match keys.
140
+ #
141
+ # This implementation is __different__ than ActiveSupport:
142
+ # __it doesn't accept Regular expressions__, because the Redis matcher is designed
143
+ # only for strings with wildcards.
144
+ def key_matcher(pattern, options)
145
+ prefix = options[:namespace].is_a?(Proc) ? options[:namespace].call : options[:namespace]
146
+ if prefix
147
+ raise "Regexps aren't supported, please use string with wildcards." if pattern.is_a?(Regexp)
148
+ "#{prefix}:#{pattern}"
149
+ else
150
+ pattern
151
+ end
152
+ end
153
+ end
154
+
155
+ module Store
156
+ include ::Redis::Store.rails3? ? Rails3 : Rails2
157
+ end
158
+ end
159
+ end
160
+
161
+ module ActiveSupport
162
+ module Cache
163
+ class RedisStore < Store
164
+ include ::RedisStore::Cache::Store
165
+
166
+ # Reads multiple keys from the cache using a single call to the
167
+ # servers for all keys. Options can be passed in the last argument.
168
+ #
169
+ # Example:
170
+ # cache.read_multi "rabbit", "white-rabbit"
171
+ # cache.read_multi "rabbit", "white-rabbit", :raw => true
172
+ def read_multi(*names)
173
+ values = @data.mget(*names)
174
+
175
+ # Remove the options hash before mapping keys to values
176
+ names.extract_options!
177
+
178
+ result = Hash[names.zip(values)]
179
+ result.reject!{ |k,v| v.nil? }
180
+ result
181
+ end
182
+
183
+ # Increment a key in the store.
184
+ #
185
+ # If the key doesn't exist it will be initialized on 0.
186
+ # If the key exist but it isn't a Fixnum it will be initialized on 0.
187
+ #
188
+ # Example:
189
+ # We have two objects in cache:
190
+ # counter # => 23
191
+ # rabbit # => #<Rabbit:0x5eee6c>
192
+ #
193
+ # cache.increment "counter"
194
+ # cache.read "counter", :raw => true # => "24"
195
+ #
196
+ # cache.increment "counter", 6
197
+ # cache.read "counter", :raw => true # => "30"
198
+ #
199
+ # cache.increment "a counter"
200
+ # cache.read "a counter", :raw => true # => "1"
201
+ #
202
+ # cache.increment "rabbit"
203
+ # cache.read "rabbit", :raw => true # => "1"
204
+ def increment(key, amount = 1)
205
+ instrument(:increment, key, :amount => amount) do
206
+ @data.incrby key, amount
207
+ end
208
+ end
209
+
210
+ # Decrement a key in the store
211
+ #
212
+ # If the key doesn't exist it will be initialized on 0.
213
+ # If the key exist but it isn't a Fixnum it will be initialized on 0.
214
+ #
215
+ # Example:
216
+ # We have two objects in cache:
217
+ # counter # => 23
218
+ # rabbit # => #<Rabbit:0x5eee6c>
219
+ #
220
+ # cache.decrement "counter"
221
+ # cache.read "counter", :raw => true # => "22"
222
+ #
223
+ # cache.decrement "counter", 2
224
+ # cache.read "counter", :raw => true # => "20"
225
+ #
226
+ # cache.decrement "a counter"
227
+ # cache.read "a counter", :raw => true # => "-1"
228
+ #
229
+ # cache.decrement "rabbit"
230
+ # cache.read "rabbit", :raw => true # => "-1"
231
+ def decrement(key, amount = 1)
232
+ instrument(:decrement, key, :amount => amount) do
233
+ @data.decrby key, amount
234
+ end
235
+ end
236
+
237
+ # Clear all the data from the store.
238
+ def clear
239
+ instrument(:clear, nil, nil) do
240
+ @data.flushdb
241
+ end
242
+ end
243
+
244
+ def stats
245
+ @data.info
246
+ end
247
+
248
+ # Force client reconnection, useful Unicorn deployed apps.
249
+ def reconnect
250
+ @data.reconnect
251
+ end
252
+ end
253
+ end
254
+ end
@@ -0,0 +1,79 @@
1
+ module Merb
2
+ module Cache
3
+ class RedisStore < AbstractStore
4
+ # Instantiate the store.
5
+ #
6
+ # Example:
7
+ # RedisStore.new
8
+ # # => host: localhost, port: 6379, db: 0
9
+ #
10
+ # RedisStore.new :servers => ["example.com"]
11
+ # # => host: example.com, port: 6379, db: 0
12
+ #
13
+ # RedisStore.new :servers => ["example.com:23682"]
14
+ # # => host: example.com, port: 23682, db: 0
15
+ #
16
+ # RedisStore.new :servers => ["example.com:23682/1"]
17
+ # # => host: example.com, port: 23682, db: 1
18
+ #
19
+ # RedisStore.new :servers => ["example.com:23682/1/theplaylist"]
20
+ # # => host: example.com, port: 23682, db: 1, namespace: theplaylist
21
+ #
22
+ # RedisStore.new :servers => ["localhost:6379/0", "localhost:6380/0"]
23
+ # # => instantiate a cluster
24
+ def initialize(config = { })
25
+ @data = Redis::Factory.create config[:servers]
26
+ end
27
+
28
+ def writable?(key, parameters = {}, conditions = {})
29
+ true
30
+ end
31
+
32
+ def read(key, parameters = {}, conditions = {})
33
+ @data.get normalize(key, parameters), conditions
34
+ end
35
+
36
+ def write(key, data = nil, parameters = {}, conditions = {})
37
+ if writable?(key, parameters, conditions)
38
+ method = conditions && conditions[:unless_exist] ? :setnx : :set
39
+ @data.send method, normalize(key, parameters), data, conditions
40
+ end
41
+ end
42
+
43
+ def write_all(key, data = nil, parameters = {}, conditions = {})
44
+ write key, data, parameters, conditions
45
+ end
46
+
47
+ def fetch(key, parameters = {}, conditions = {}, &blk)
48
+ (data = read(key, parameters)) || block_given? && begin
49
+ data = yield
50
+ write(key, data, parameters, conditions)
51
+ end
52
+ data || nil
53
+ end
54
+
55
+ def exists?(key, parameters = {})
56
+ @data.exists normalize(key, parameters)
57
+ end
58
+
59
+ def delete(key, parameters = {})
60
+ @data.del normalize(key, parameters)
61
+ end
62
+
63
+ def delete_all
64
+ @data.flushdb
65
+ end
66
+
67
+ def delete_all!
68
+ delete_all
69
+ end
70
+
71
+ private
72
+ # Returns cache key calculated from base key
73
+ # and SHA2 hex from parameters.
74
+ def normalize(key, parameters = {})
75
+ parameters.empty? ? "#{key}" : "#{key}--#{parameters.to_sha2}"
76
+ end
77
+ end
78
+ end
79
+ end