honkster-redis-store 0.3.10

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.
@@ -0,0 +1,32 @@
1
+ module Merb
2
+ # HACK for cyclic dependency: redis-store is required before Merb session stores
3
+ class Mash < Hash; end
4
+ class SessionContainer < Mash; class_inheritable_accessor :session_store_type end
5
+ class SessionStoreContainer < SessionContainer; end
6
+
7
+ class RedisSession < SessionStoreContainer
8
+ self.session_store_type = :redis
9
+ end
10
+
11
+ module RedisStore
12
+ def retrieve_session(session_id)
13
+ get("session:#{session_id}")
14
+ end
15
+
16
+ def store_session(session_id, data)
17
+ set("session:#{session_id}", data)
18
+ end
19
+
20
+ def delete_session(session_id)
21
+ delete("session:#{session_id}")
22
+ end
23
+ end
24
+ end
25
+
26
+ module Rack
27
+ module Session
28
+ class Redis
29
+ include Merb::RedisStore
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,81 @@
1
+ module Rack
2
+ module Session
3
+ class Redis < Abstract::ID
4
+ attr_reader :mutex, :pool
5
+ DEFAULT_OPTIONS = Abstract::ID::DEFAULT_OPTIONS.merge :redis_server => "localhost:6379"
6
+
7
+ def initialize(app, options = {})
8
+ super
9
+ @mutex = Mutex.new
10
+ @pool = RedisFactory.create options[:redis_server] || @default_options[:redis_server]
11
+ end
12
+
13
+ def generate_sid
14
+ loop do
15
+ sid = super
16
+ break sid unless @pool.marshalled_get(sid)
17
+ end
18
+ end
19
+
20
+ def get_session(env, sid)
21
+ session = @pool.marshalled_get(sid) if sid
22
+ @mutex.lock if env['rack.multithread']
23
+ unless sid and session
24
+ env['rack.errors'].puts("Session '#{sid.inspect}' not found, initializing...") if $VERBOSE and not sid.nil?
25
+ session = {}
26
+ sid = generate_sid
27
+ ret = @pool.marshalled_set sid, session
28
+ raise "Session collision on '#{sid.inspect}'" unless ret
29
+ end
30
+ session.instance_variable_set('@old', {}.merge(session))
31
+ return [sid, session]
32
+ rescue Errno::ECONNREFUSED
33
+ warn "#{self} is unable to find server."
34
+ warn $!.inspect
35
+ return [ nil, {} ]
36
+ ensure
37
+ @mutex.unlock if env['rack.multithread']
38
+ end
39
+
40
+ def set_session(env, session_id, new_session, options)
41
+ @mutex.lock if env['rack.multithread']
42
+ session = @pool.marshalled_get(session_id) rescue {}
43
+ if options[:renew] or options[:drop]
44
+ @pool.del session_id
45
+ return false if options[:drop]
46
+ session_id = generate_sid
47
+ @pool.marshalled_set session_id, 0
48
+ end
49
+ old_session = new_session.instance_variable_get('@old') || {}
50
+ session = merge_sessions session_id, old_session, new_session, session
51
+ @pool.marshalled_set session_id, session, options
52
+ return session_id
53
+ rescue Errno::ECONNREFUSED
54
+ warn "#{self} is unable to find server."
55
+ warn $!.inspect
56
+ return false
57
+ ensure
58
+ @mutex.unlock if env['rack.multithread']
59
+ end
60
+
61
+ private
62
+ def merge_sessions(sid, old, new, cur=nil)
63
+ cur ||= {}
64
+ unless Hash === old and Hash === new
65
+ warn 'Bad old or new sessions provided.'
66
+ return cur
67
+ end
68
+
69
+ delete = old.keys - new.keys
70
+ warn "//@#{sid}: dropping #{delete*','}" if $DEBUG and not delete.empty?
71
+ delete.each{|k| cur.del k }
72
+
73
+ update = new.keys.select{|k| new[k] != old[k] }
74
+ warn "//@#{sid}: updating #{update*','}" if $DEBUG and not update.empty?
75
+ update.each{|k| cur[k] = new[k] }
76
+
77
+ cur
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,16 @@
1
+ class DistributedMarshaledRedis < DistRedis
2
+ attr_reader :ring
3
+
4
+ def initialize(addresses)
5
+ nodes = addresses.map do |address|
6
+ MarshaledRedis.new address
7
+ end
8
+ @ring = Redis::HashRing.new nodes
9
+ end
10
+
11
+ def nodes
12
+ ring.nodes
13
+ end
14
+
15
+ alias_method :flushdb, :delete_cloud!
16
+ end
@@ -0,0 +1,52 @@
1
+ class MarshaledRedis < Redis::Client
2
+ def marshalled_set(key, val, options = nil)
3
+ val = marshal_value(val, options)
4
+ if expires_in = expires_in(options)
5
+ set_with_expire key, val, expires_in
6
+ else
7
+ set key, val
8
+ end
9
+ end
10
+
11
+ def marshalled_setnx(key, val, options = nil)
12
+ val = marshal_value(val, options)
13
+ if expires_in = expires_in(options)
14
+ setnx_with_expire key, val, expires_in
15
+ else
16
+ setnx key, val
17
+ end
18
+ end
19
+
20
+ def setnx_with_expire(key, value, ttl)
21
+ multi do
22
+ setnx(key, val)
23
+ expire(key, expires_in)
24
+ end
25
+ end
26
+
27
+ def marshalled_get(key, options = nil)
28
+ result = call_command([:get, key])
29
+ result = Marshal.load result if unmarshal?(result, options)
30
+ result
31
+ end
32
+
33
+ private
34
+ def marshal_value(val, options)
35
+ raw?(options) ? val : Marshal.dump(val)
36
+ end
37
+
38
+ def unmarshal?(result, options)
39
+ result && result.size > 0 && !raw?(options)
40
+ end
41
+
42
+ def raw?(options)
43
+ options && options[:raw]
44
+ end
45
+
46
+ def expires_in(options)
47
+ if options
48
+ # Rack::Session Merb Rails/Sinatra
49
+ options[:expire_after] || options[:expires_in] || options[:expire_in]
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,26 @@
1
+ class RedisFactory
2
+ class << self
3
+ def 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
+ DistributedMarshaledRedis.new redis_client_options
10
+ else
11
+ MarshaledRedis.new redis_client_options.first || {}
12
+ end
13
+ end
14
+
15
+ def convert_to_redis_client_options(address_or_options)
16
+ return address_or_options if address_or_options.is_a?(Hash)
17
+ host, port = address_or_options.split /\:/
18
+ port, db = port.split /\// if port
19
+ options = {}
20
+ options[:host] = host if host
21
+ options[:port] = port if port
22
+ options[:db] = db.to_i if db
23
+ options
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,33 @@
1
+ require "redis"
2
+ require "redis/dist_redis"
3
+ require "redis/redis_factory"
4
+ require "redis/marshaled_redis"
5
+ require "redis/distributed_marshaled_redis"
6
+
7
+ # Cache store
8
+ if defined?(Sinatra)
9
+ require "cache/sinatra/redis_store"
10
+ elsif defined?(Merb)
11
+ # HACK for cyclic dependency: redis-store is required before merb-cache
12
+ module Merb; module Cache; class AbstractStore; end end end
13
+ require "cache/merb/redis_store"
14
+ elsif defined?(Rails)
15
+ require "cache/rails/redis_store"
16
+ require "cache/rails/redis_session_store"
17
+ end
18
+
19
+ # Rack::Session
20
+ if defined?(Rack::Session)
21
+ require "rack/session/abstract/id"
22
+ require "rack/session/redis"
23
+ if defined?(Merb)
24
+ require "rack/session/merb"
25
+ end
26
+ end
27
+
28
+ # Rack::Cache
29
+ if defined?(Rack::Cache)
30
+ require "rack/cache/key"
31
+ require "rack/cache/redis_metastore"
32
+ require "rack/cache/redis_entitystore"
33
+ end
@@ -0,0 +1,80 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{redis-store}
8
+ s.version = "0.3.7"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Luca Guidi"]
12
+ s.date = %q{2009-11-15}
13
+ s.description = %q{Rack::Session, Rack::Cache and cache Redis stores for Ruby web frameworks.}
14
+ s.email = %q{guidi.luca@gmail.com}
15
+ s.extra_rdoc_files = [
16
+ "README.md"
17
+ ]
18
+ s.files = [
19
+ ".gitignore",
20
+ "Gemfile",
21
+ "MIT-LICENSE",
22
+ "README.md",
23
+ "Rakefile",
24
+ "VERSION",
25
+ "lib/cache/merb/redis_store.rb",
26
+ "lib/cache/rails/redis_store.rb",
27
+ "lib/cache/sinatra/redis_store.rb",
28
+ "lib/rack/cache/redis_entitystore.rb",
29
+ "lib/rack/cache/redis_metastore.rb",
30
+ "lib/rack/session/merb.rb",
31
+ "lib/rack/session/redis.rb",
32
+ "lib/redis-store.rb",
33
+ "lib/redis/distributed_marshaled_redis.rb",
34
+ "lib/redis/marshaled_redis.rb",
35
+ "lib/redis/redis_factory.rb",
36
+ "redis-store.gemspec",
37
+ "spec/cache/merb/redis_store_spec.rb",
38
+ "spec/cache/rails/redis_store_spec.rb",
39
+ "spec/cache/sinatra/redis_store_spec.rb",
40
+ "spec/config/master.conf",
41
+ "spec/config/single.conf",
42
+ "spec/config/slave.conf",
43
+ "spec/rack/cache/entitystore/pony.jpg",
44
+ "spec/rack/cache/entitystore/redis_spec.rb",
45
+ "spec/rack/cache/metastore/redis_spec.rb",
46
+ "spec/rack/session/redis_spec.rb",
47
+ "spec/redis/distributed_marshaled_redis_spec.rb",
48
+ "spec/redis/marshaled_redis_spec.rb",
49
+ "spec/redis/redis_factory_spec.rb",
50
+ "spec/spec_helper.rb",
51
+ "tasks/redis.tasks.rb"
52
+ ]
53
+ s.homepage = %q{http://github.com/jodosha/redis-store}
54
+ s.rdoc_options = ["--charset=UTF-8"]
55
+ s.require_paths = ["lib"]
56
+ s.rubygems_version = %q{1.3.5}
57
+ s.summary = %q{Rack::Session, Rack::Cache and cache Redis stores for Ruby web frameworks.}
58
+ s.test_files = [
59
+ "spec/cache/merb/redis_store_spec.rb",
60
+ "spec/cache/rails/redis_store_spec.rb",
61
+ "spec/cache/sinatra/redis_store_spec.rb",
62
+ "spec/rack/cache/entitystore/redis_spec.rb",
63
+ "spec/rack/cache/metastore/redis_spec.rb",
64
+ "spec/rack/session/redis_spec.rb",
65
+ "spec/redis/distributed_marshaled_redis_spec.rb",
66
+ "spec/redis/marshaled_redis_spec.rb",
67
+ "spec/redis/redis_factory_spec.rb",
68
+ "spec/spec_helper.rb"
69
+ ]
70
+
71
+ if s.respond_to? :specification_version then
72
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
73
+ s.specification_version = 3
74
+
75
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
76
+ else
77
+ end
78
+ else
79
+ end
80
+ end
@@ -0,0 +1,140 @@
1
+ require File.join(File.dirname(__FILE__), "/../../spec_helper")
2
+
3
+ module Merb
4
+ module Cache
5
+ describe "Merb::Cache::RedisStore" do
6
+ before(:each) do
7
+ @store = Merb::Cache::RedisStore.new
8
+ @dstore = Merb::Cache::RedisStore.new :servers => ["localhost:6380/1", "localhost: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 "rub-a-dub"
14
+ end
15
+ end
16
+
17
+ it "should accept connection params" do
18
+ redis = instantiate_store
19
+ redis.to_s.should == "Redis Client connected to 127.0.0.1:6379 against DB 0"
20
+
21
+ redis = instantiate_store "localhost"
22
+ redis.to_s.should == "Redis Client connected to localhost:6379 against DB 0"
23
+
24
+ redis = instantiate_store "localhost:6380"
25
+ redis.to_s.should == "Redis Client connected to localhost:6380 against DB 0"
26
+
27
+ redis = instantiate_store "localhost:6380/13"
28
+ redis.to_s.should == "Redis Client connected to localhost:6380 against DB 13"
29
+ end
30
+
31
+ it "should instantiate a ring" do
32
+ store = instantiate_store
33
+ store.should be_kind_of(MarshaledRedis)
34
+ store = instantiate_store ["localhost:6379/0", "localhost:6379/1"]
35
+ store.should be_kind_of(DistributedMarshaledRedis)
36
+ end
37
+
38
+ it "should verify if writable" do
39
+ with_store_management do |store|
40
+ store.writable?("rabbit").should be_true
41
+ end
42
+ end
43
+
44
+ it "should read the data" do
45
+ with_store_management do |store|
46
+ store.read("rabbit").should === @rabbit
47
+ end
48
+ end
49
+
50
+ it "should read raw data" do
51
+ with_store_management do |store|
52
+ store.read("rabbit", {}, :raw => true).should == "\004\bU:\017OpenStruct{\006:\tname\"\nbunny"
53
+ end
54
+ end
55
+
56
+ it "should write the data" do
57
+ with_store_management do |store|
58
+ store.write "rabbit", @white_rabbit
59
+ store.read("rabbit").should === @white_rabbit
60
+ end
61
+ end
62
+
63
+ it "should write raw data" do
64
+ with_store_management do |store|
65
+ store.write "rabbit", @white_rabbit, {}, :raw => true
66
+ store.read("rabbit", {}, :raw => true).should == %(#<OpenStruct color="white">)
67
+ end
68
+ end
69
+
70
+ it "should write the data with expiration time" do
71
+ with_store_management do |store|
72
+ store.write "rabbit", @white_rabbit, {}, :expires_in => 1.second
73
+ store.read("rabbit").should === @white_rabbit ; sleep 2
74
+ store.read("rabbit").should be_nil
75
+ end
76
+ end
77
+
78
+ it "should not write data if :unless_exist option is true" do
79
+ with_store_management do |store|
80
+ store.write "rabbit", @white_rabbit, {}, :unless_exist => true
81
+ store.read("rabbit").should === @rabbit
82
+ end
83
+ end
84
+
85
+ it "should write all the data" do
86
+ with_store_management do |store|
87
+ store.write_all "rabbit", @white_rabbit
88
+ store.read("rabbit").should === @white_rabbit
89
+ end
90
+ end
91
+
92
+ it "should fetch data" do
93
+ with_store_management do |store|
94
+ store.fetch("rabbit").should == @rabbit
95
+ store.fetch("rub-a-dub").should be_nil
96
+ store.fetch("rub-a-dub") { "Flora de Cana" }
97
+ store.fetch("rub-a-dub").should === "Flora de Cana"
98
+ end
99
+ end
100
+
101
+ it "should verify existence" do
102
+ with_store_management do |store|
103
+ store.exists?("rabbit").should be_true
104
+ store.exists?("rab-a-dub").should be_false
105
+ end
106
+ end
107
+
108
+ it "should delete data" do
109
+ with_store_management do |store|
110
+ store.delete "rabbit"
111
+ store.read("rabbit").should be_nil
112
+ end
113
+ end
114
+
115
+ it "should delete all the data" do
116
+ with_store_management do |store|
117
+ store.delete_all
118
+ store.instance_variable_get(:@data).keys("*").flatten.should be_empty
119
+ end
120
+ end
121
+
122
+ it "should delete all the data with bang method" do
123
+ with_store_management do |store|
124
+ store.delete_all!
125
+ store.instance_variable_get(:@data).keys("*").flatten.should be_empty
126
+ end
127
+ end
128
+
129
+ private
130
+ def instantiate_store(addresses = nil)
131
+ Merb::Cache::RedisStore.new(:servers => [addresses].flatten).instance_variable_get(:@data)
132
+ end
133
+
134
+ def with_store_management
135
+ yield @store
136
+ yield @dstore
137
+ end
138
+ end
139
+ end
140
+ end
@@ -0,0 +1,77 @@
1
+ require File.join(File.dirname(__FILE__), "/../../spec_helper")
2
+
3
+ module ActionController
4
+ module Session
5
+ describe "ActionController::Session::RedisSessionStore" do
6
+ attr_reader :app
7
+ before(:each) do
8
+ @app = Object.new
9
+ @store = ActionController::Session::RedisSessionStore.new(app)
10
+ @dstore = ActionController::Session::RedisSessionStore.new app, :servers => ["localhost:6380/1", "localhost:6381/1"]
11
+ @rabbit = OpenStruct.new :name => "bunny"
12
+ @white_rabbit = OpenStruct.new :color => "white"
13
+ with_store_management do |store|
14
+ class << store
15
+ attr_reader :pool
16
+ public :get_session, :set_session
17
+ end
18
+ store.set_session({'rack.session.options' => {}}, "rabbit", @rabbit)
19
+ store.pool.del "counter"
20
+ store.pool.del "rub-a-dub"
21
+ end
22
+ end
23
+
24
+ it "should accept connection params" do
25
+ redis = instantiate_store
26
+ redis.to_s.should == "Redis Client connected to 127.0.0.1:6379 against DB 0"
27
+
28
+ redis = instantiate_store :servers => "localhost"
29
+ redis.to_s.should == "Redis Client connected to localhost:6379 against DB 0"
30
+
31
+ redis = instantiate_store :servers => "localhost:6380"
32
+ redis.to_s.should == "Redis Client connected to localhost:6380 against DB 0"
33
+
34
+ redis = instantiate_store :servers => "localhost:6380/13"
35
+ redis.to_s.should == "Redis Client connected to localhost:6380 against DB 13"
36
+ end
37
+
38
+ it "should instantiate a ring" do
39
+ store = instantiate_store
40
+ store.should be_kind_of(MarshaledRedis)
41
+ store = instantiate_store :servers => ["localhost:6379/0", "localhost:6379/1"]
42
+ store.should be_kind_of(DistributedMarshaledRedis)
43
+ end
44
+
45
+ it "should read the data" do
46
+ with_store_management do |store|
47
+ store.get_session({}, "rabbit").should === ["rabbit", @rabbit]
48
+ end
49
+ end
50
+
51
+ it "should write the data" do
52
+ with_store_management do |store|
53
+ store.set_session({"rack.session.options" => {}}, "rabbit", @white_rabbit)
54
+ store.get_session({}, "rabbit").should === ["rabbit", @white_rabbit]
55
+ end
56
+ end
57
+
58
+ it "should write the data with expiration time" do
59
+ with_store_management do |store|
60
+ store.set_session({"rack.session.options" => {:expires_in => 1.second}}, "rabbit", @white_rabbit)
61
+ store.get_session({}, "rabbit").should === ["rabbit", @white_rabbit]; sleep 2
62
+ store.get_session({}, "rabbit").should === ["rabbit", {}]
63
+ end
64
+ end
65
+
66
+ private
67
+ def instantiate_store(params={})
68
+ ActionController::Session::RedisSessionStore.new(app, params).instance_variable_get(:@pool)
69
+ end
70
+
71
+ def with_store_management
72
+ yield @store
73
+ yield @dstore
74
+ end
75
+ end
76
+ end
77
+ end