nono-redis-store 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. data/.gitignore +11 -0
  2. data/CHANGELOG +262 -0
  3. data/Gemfile +33 -0
  4. data/Gemfile.lock +194 -0
  5. data/MIT-LICENSE +20 -0
  6. data/README.md +191 -0
  7. data/Rakefile +60 -0
  8. data/VERSION +1 -0
  9. data/lib/action_controller/session/redis_session_store.rb +74 -0
  10. data/lib/active_support/cache/redis_store.rb +228 -0
  11. data/lib/cache/merb/redis_store.rb +75 -0
  12. data/lib/cache/sinatra/redis_store.rb +126 -0
  13. data/lib/i18n/backend/redis.rb +67 -0
  14. data/lib/rack/cache/redis_entitystore.rb +51 -0
  15. data/lib/rack/cache/redis_metastore.rb +42 -0
  16. data/lib/rack/session/merb.rb +32 -0
  17. data/lib/rack/session/redis.rb +81 -0
  18. data/lib/redis-store.rb +44 -0
  19. data/lib/redis/distributed_store.rb +35 -0
  20. data/lib/redis/factory.rb +46 -0
  21. data/lib/redis/store.rb +30 -0
  22. data/lib/redis/store/interface.rb +17 -0
  23. data/lib/redis/store/marshalling.rb +41 -0
  24. data/lib/redis/store/namespace.rb +54 -0
  25. data/lib/redis/store/ttl.rb +37 -0
  26. data/lib/redis/store/version.rb +11 -0
  27. data/redis-store.gemspec +103 -0
  28. data/spec/action_controller/session/redis_session_store_spec.rb +121 -0
  29. data/spec/active_support/cache/redis_store_spec.rb +405 -0
  30. data/spec/cache/merb/redis_store_spec.rb +143 -0
  31. data/spec/cache/sinatra/redis_store_spec.rb +192 -0
  32. data/spec/config/master.conf +312 -0
  33. data/spec/config/single.conf +312 -0
  34. data/spec/config/slave.conf +312 -0
  35. data/spec/i18n/backend/redis_spec.rb +56 -0
  36. data/spec/rack/cache/entitystore/pony.jpg +0 -0
  37. data/spec/rack/cache/entitystore/redis_spec.rb +120 -0
  38. data/spec/rack/cache/metastore/redis_spec.rb +255 -0
  39. data/spec/rack/session/redis_spec.rb +234 -0
  40. data/spec/redis/distributed_store_spec.rb +47 -0
  41. data/spec/redis/factory_spec.rb +110 -0
  42. data/spec/redis/store/interface_spec.rb +23 -0
  43. data/spec/redis/store/marshalling_spec.rb +83 -0
  44. data/spec/redis/store/namespace_spec.rb +76 -0
  45. data/spec/redis/store/version_spec.rb +7 -0
  46. data/spec/spec_helper.rb +43 -0
  47. data/tasks/redis.tasks.rb +220 -0
  48. metadata +141 -0
@@ -0,0 +1,44 @@
1
+ require "redis"
2
+ require "redis/distributed"
3
+ require "redis/factory"
4
+ require "redis/store/interface"
5
+ require "redis/store/ttl"
6
+ require "redis/store/namespace"
7
+ require "redis/store/marshalling"
8
+ require "redis/store/version"
9
+ require "redis/store"
10
+ require "redis/distributed_store"
11
+
12
+ # Cache store
13
+ if defined?(Sinatra)
14
+ require "cache/sinatra/redis_store"
15
+ elsif defined?(Merb)
16
+ # HACK for cyclic dependency: redis-store is required before merb-cache
17
+ module Merb; module Cache; class AbstractStore; end end end
18
+ require "cache/merb/redis_store"
19
+ end
20
+
21
+ # Rack::Session
22
+ if defined?(Rack::Session)
23
+ require "rack/session/abstract/id"
24
+ require "rack/session/redis"
25
+ if defined?(Merb)
26
+ require "rack/session/merb"
27
+ end
28
+ end
29
+
30
+ # ActionDispatch::Session
31
+ if ::Redis::Store.rails3?
32
+ require "action_controller/session/redis_session_store"
33
+ end
34
+
35
+ # Rack::Cache
36
+ if defined?(Rack::Cache)
37
+ require "rack/cache/key"
38
+ require "rack/cache/redis_metastore"
39
+ require "rack/cache/redis_entitystore"
40
+ end
41
+
42
+ if defined?(I18n)
43
+ require "i18n/backend/redis"
44
+ end
@@ -0,0 +1,35 @@
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 set(key, value, options = nil)
18
+ node_for(key).set(key, value, options)
19
+ end
20
+
21
+ def get(key, options = nil)
22
+ node_for(key).get(key, options)
23
+ end
24
+
25
+ def setnx(key, value, options = nil)
26
+ node_for(key).setnx(key, value, options)
27
+ end
28
+
29
+ private
30
+ def _extend_namespace(options)
31
+ @namespace = options[:namespace]
32
+ extend ::Redis::Store::Namespace if @namespace
33
+ end
34
+ end
35
+ 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
+ options[:marshalling] = false if RUBY_VERSION =~ /1\.9/
42
+ options
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,30 @@
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 to_s
16
+ "Redis Client connected to #{@client.host}:#{@client.port} against DB #{@client.db}"
17
+ end
18
+
19
+ private
20
+ def _extend_marshalling(options)
21
+ @marshalling = !(options[:marshalling] === false) # HACK - TODO delegate to Factory
22
+ extend Marshalling if @marshalling
23
+ end
24
+
25
+ def _extend_namespace(options)
26
+ @namespace = options[:namespace]
27
+ extend Namespace if @namespace
28
+ end
29
+ end
30
+ end
@@ -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,41 @@
1
+ class Redis
2
+ class Store < self
3
+ module Marshalling
4
+ def set(key, value, options = nil)
5
+ _marshal(value, options) { |value| super key, value, options }
6
+ end
7
+
8
+ def setnx(key, value, options = nil)
9
+ _marshal(value, options) { |value| super key, 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) ? val : Marshal.dump(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
+ end
40
+ end
41
+ end
@@ -0,0 +1,54 @@
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) }
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(%r{^#{@namespace}\:}) ? key : "#{@namespace}:#{key}"
51
+ end
52
+ end
53
+ end
54
+ 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,11 @@
1
+ class Redis
2
+ class Store < self
3
+ module VERSION #:nodoc:
4
+ MAJOR = 1
5
+ MINOR = 0
6
+ TINY = 0
7
+
8
+ STRING = [MAJOR, MINOR, TINY].join('.')
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,103 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{nono-redis-store}
8
+ s.version = "1.0.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new("> 1.3.1") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Luca Guidi", "Bruno Michel"]
12
+ s.date = %q{2010-11-15}
13
+ s.description = %q{Namespaced Rack::Session, Rack::Cache, I18n and cache Redis stores for Ruby web frameworks.}
14
+ s.email = %q{brmichel@free.fr}
15
+ s.extra_rdoc_files = [
16
+ "README.md"
17
+ ]
18
+ s.files = [
19
+ ".gitignore",
20
+ "CHANGELOG",
21
+ "Gemfile",
22
+ "Gemfile.lock",
23
+ "MIT-LICENSE",
24
+ "README.md",
25
+ "Rakefile",
26
+ "VERSION",
27
+ "lib/action_controller/session/redis_session_store.rb",
28
+ "lib/active_support/cache/redis_store.rb",
29
+ "lib/cache/merb/redis_store.rb",
30
+ "lib/cache/sinatra/redis_store.rb",
31
+ "lib/i18n/backend/redis.rb",
32
+ "lib/rack/cache/redis_entitystore.rb",
33
+ "lib/rack/cache/redis_metastore.rb",
34
+ "lib/rack/session/merb.rb",
35
+ "lib/rack/session/redis.rb",
36
+ "lib/redis-store.rb",
37
+ "lib/redis/distributed_store.rb",
38
+ "lib/redis/factory.rb",
39
+ "lib/redis/store.rb",
40
+ "lib/redis/store/interface.rb",
41
+ "lib/redis/store/marshalling.rb",
42
+ "lib/redis/store/namespace.rb",
43
+ "lib/redis/store/ttl.rb",
44
+ "lib/redis/store/version.rb",
45
+ "redis-store.gemspec",
46
+ "spec/action_controller/session/redis_session_store_spec.rb",
47
+ "spec/active_support/cache/redis_store_spec.rb",
48
+ "spec/cache/merb/redis_store_spec.rb",
49
+ "spec/cache/sinatra/redis_store_spec.rb",
50
+ "spec/config/master.conf",
51
+ "spec/config/single.conf",
52
+ "spec/config/slave.conf",
53
+ "spec/i18n/backend/redis_spec.rb",
54
+ "spec/rack/cache/entitystore/pony.jpg",
55
+ "spec/rack/cache/entitystore/redis_spec.rb",
56
+ "spec/rack/cache/metastore/redis_spec.rb",
57
+ "spec/rack/session/redis_spec.rb",
58
+ "spec/redis/distributed_store_spec.rb",
59
+ "spec/redis/factory_spec.rb",
60
+ "spec/redis/store/interface_spec.rb",
61
+ "spec/redis/store/marshalling_spec.rb",
62
+ "spec/redis/store/namespace_spec.rb",
63
+ "spec/redis/store/version_spec.rb",
64
+ "spec/spec_helper.rb",
65
+ "tasks/redis.tasks.rb"
66
+ ]
67
+ s.homepage = %q{http://github.com/nono/redis-store/tree/nono}
68
+ s.rdoc_options = ["--charset=UTF-8"]
69
+ s.require_paths = ["lib"]
70
+ s.rubygems_version = %q{1.3.7}
71
+ s.summary = %q{Namespaced Rack::Session, Rack::Cache, I18n and cache Redis stores for Ruby web frameworks.}
72
+ s.test_files = [
73
+ "spec/action_controller/session/redis_session_store_spec.rb",
74
+ "spec/active_support/cache/redis_store_spec.rb",
75
+ "spec/cache/merb/redis_store_spec.rb",
76
+ "spec/cache/sinatra/redis_store_spec.rb",
77
+ "spec/i18n/backend/redis_spec.rb",
78
+ "spec/rack/cache/entitystore/redis_spec.rb",
79
+ "spec/rack/cache/metastore/redis_spec.rb",
80
+ "spec/rack/session/redis_spec.rb",
81
+ "spec/redis/distributed_store_spec.rb",
82
+ "spec/redis/factory_spec.rb",
83
+ "spec/redis/store/interface_spec.rb",
84
+ "spec/redis/store/marshalling_spec.rb",
85
+ "spec/redis/store/namespace_spec.rb",
86
+ "spec/redis/store/version_spec.rb",
87
+ "spec/spec_helper.rb"
88
+ ]
89
+
90
+ if s.respond_to? :specification_version then
91
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
92
+ s.specification_version = 3
93
+
94
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
95
+ s.add_runtime_dependency(%q<redis>, [">= 2.0.0"])
96
+ else
97
+ s.add_dependency(%q<redis>, [">= 2.0.0"])
98
+ end
99
+ else
100
+ s.add_dependency(%q<redis>, [">= 2.0.0"])
101
+ end
102
+ end
103
+
@@ -0,0 +1,121 @@
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 127.0.0.1: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 localhost:6380 against DB 0"
49
+
50
+ redis = instantiate_store :servers => [{ :db => 13 }]
51
+ redis.to_s.should == "Redis Client connected to localhost:6379 against DB 13"
52
+
53
+ redis = instantiate_store :servers => [{ :key_prefix => "theplaylist" }]
54
+ redis.to_s.should == "Redis Client connected to localhost:6379 against DB 0 with namespace theplaylist"
55
+ end
56
+
57
+ it "should instantiate a ring" do
58
+ store = instantiate_store
59
+ store.should be_kind_of(Redis::Store)
60
+ store = instantiate_store :servers => ["redis://127.0.0.1:6379/0", "redis://127.0.0.1:6379/1"]
61
+ store.should be_kind_of(Redis::DistributedStore)
62
+ end
63
+
64
+ it "should read the data" do
65
+ with_store_management do |store|
66
+ store.get_session(@env, @sid).should === [@sid, @rabbit]
67
+ end
68
+ end
69
+
70
+ it "should write the data" do
71
+ with_store_management do |store|
72
+ store.set_session(@env, @sid, @white_rabbit)
73
+ store.get_session(@env, @sid).should === [@sid, @white_rabbit]
74
+ end
75
+ end
76
+
77
+ it "should delete the data" do
78
+ with_store_management do |store|
79
+ store.destroy(@env)
80
+ store.get_session(@env, @sid).should === [@sid, {}]
81
+ end
82
+ end
83
+
84
+ it "should write the data with expiration time" do
85
+ with_store_management do |store|
86
+ @env['rack.session.options'].merge!(:expires_in => 1.second)
87
+ store.set_session(@env, @sid, @white_rabbit)
88
+ store.get_session(@env, @sid).should === [@sid, @white_rabbit]; sleep 2
89
+ store.get_session(@env, @sid).should === [@sid, {}]
90
+ end
91
+ end
92
+
93
+ describe "namespace" do
94
+ before :each do
95
+ @namespace = "theplaylist"
96
+ @store = RAILS_SESSION_STORE_CLASS.new(lambda {|| }, :servers => [{ :namespace => @namespace }])
97
+ @pool = @store.instance_variable_get(:@pool)
98
+ @client = @pool.instance_variable_get(:@client)
99
+ end
100
+
101
+ it "should read the data" do
102
+ @client.should_receive(:call).with(:get, "#{@namespace}:#{@sid}")
103
+ @store.send :get_session, @env, @sid
104
+ end
105
+
106
+ it "should write the data" do
107
+ @client.should_receive(:call).with(:set, "#{@namespace}:#{@sid}", Marshal.dump(@white_rabbit))
108
+ @store.send :set_session, @env, @sid, @white_rabbit
109
+ end
110
+ end
111
+
112
+ private
113
+ def instantiate_store(params = { })
114
+ RAILS_SESSION_STORE_CLASS.new(app, params).instance_variable_get(:@pool)
115
+ end
116
+
117
+ def with_store_management
118
+ yield @store
119
+ yield @dstore
120
+ end
121
+ end