rack-session-sequel 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ *.gem
2
+ *.swp
3
+ .bundle
4
+ Gemfile.lock
5
+ pkg/*
data/.travis.yml ADDED
@@ -0,0 +1,7 @@
1
+ language: ruby
2
+ script: "bacon -I./lib spec/spec_session_sequel.rb"
3
+ rvm:
4
+ - 1.8.7
5
+ - 1.9.2
6
+ - 1.9.3
7
+ - jruby
data/CHANGES.md ADDED
@@ -0,0 +1,6 @@
1
+ CHANGES
2
+ =======
3
+
4
+ ### [0.0.1](https://github.com/migrs/rack-session-sequel/tree/v0.0.1) / 2012-01-29
5
+
6
+ * Initial Release
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in rack-session-sequel.gemspec
4
+ gemspec
data/README.md ADDED
@@ -0,0 +1,43 @@
1
+ rack-session-sequel
2
+ ====================
3
+
4
+ Rack session store for Sequel
5
+
6
+ <http://github.com/migrs/rack-session-sequel>
7
+
8
+ [![Build Status](https://secure.travis-ci.org/migrs/rack-session-sequel.png)](http://travis-ci.org/migrs/rack-session-sequel)
9
+
10
+ ## Installation
11
+
12
+ gem install rack-session-sequel
13
+
14
+ ## Usage
15
+
16
+ Simple (In-Memory Sequel Database)
17
+
18
+ use Rack::Session::Sequel
19
+
20
+ Specify DB URI (see [Connecting to a database](http://sequel.rubyforge.org/rdoc/files/doc/opening_databases_rdoc.html))
21
+
22
+ use Rack::Session::Sequel, 'sqlite://blog.db'
23
+
24
+ Set Sequel DB instance
25
+
26
+ DB = Sequel.connect('sqlite://blog.db')
27
+ use Rack::Session::Sequel, DB
28
+
29
+ With some config
30
+
31
+ use Rack::Session::Sequel, :db => DB, :expire_after => 600
32
+ use Rack::Session::Sequel, :db_uri => 'sqlite://blog.db', :expire_after => 600
33
+
34
+ Specify session table name
35
+
36
+ use Rack::Session::Sequel, :db => DB, :table_name => :tbl_session
37
+
38
+ ## About Sequel
39
+
40
+ - <http://sequel.rubyforge.org/>
41
+
42
+ ## License
43
+ [rack-session-sequel](http://github.com/migrs/rack-session-sequel) is Copyright (c) 2012 [Masato Igarashi](http://github.com/migrs)(@[migrs](http://twitter.com/migrs)) and distributed under the [MIT license](http://www.opensource.org/licenses/mit-license).
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,2 @@
1
+ require 'rack/session/sequel'
2
+ require 'rack-session-sequel/version'
@@ -0,0 +1,7 @@
1
+ module Rack
2
+ module Session
3
+ class Sequel
4
+ VERSION = "0.0.1"
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,104 @@
1
+ require 'rack/session/abstract/id'
2
+ require 'sequel'
3
+
4
+ module Rack
5
+ module Session
6
+ class Sequel < Abstract::ID
7
+
8
+ attr_reader :mutex, :pool
9
+
10
+ DEFAULT_OPTIONS = Abstract::ID::DEFAULT_OPTIONS.merge \
11
+ :table_name => :sessions, :db_uri => 'sqlite:/'
12
+
13
+ def initialize(app, options={})
14
+ options = {:db => options } if options.kind_of? ::Sequel::Database
15
+ options = {:db_uri => options } if options.is_a? String
16
+ super
17
+ @pool = setup_database[@default_options[:table_name]]
18
+ @mutex = Mutex.new
19
+ end
20
+
21
+ def generate_sid
22
+ loop do
23
+ sid = super
24
+ break sid unless _exists? sid
25
+ end
26
+ end
27
+
28
+ def get_session(env, sid)
29
+ with_lock(env, [nil, {}]) do
30
+ unless sid and session = _get(sid)
31
+ sid, session = generate_sid, {}
32
+ _put sid, session
33
+ end
34
+ [sid, session]
35
+ end
36
+ end
37
+
38
+ def set_session(env, session_id, new_session, options)
39
+ with_lock(env, false) do
40
+ _put session_id, new_session
41
+ session_id
42
+ end
43
+ end
44
+
45
+ def destroy_session(env, session_id, options)
46
+ with_lock(env) do
47
+ _delete(session_id)
48
+ generate_sid unless options[:drop]
49
+ end
50
+ end
51
+
52
+ def with_lock(env, default=nil)
53
+ @mutex.lock if env['rack.multithread']
54
+ yield
55
+ rescue
56
+ default
57
+ ensure
58
+ @mutex.unlock if @mutex.locked?
59
+ end
60
+
61
+ private
62
+ def setup_database
63
+ (@default_options[:db] || ::Sequel.connect(@default_options[:db_uri])).tap do |db|
64
+ db.create_table @default_options[:table_name] do
65
+ #primary_key :id
66
+ String :sid, :unique => true, :null => false, :primary_key => true
67
+ text :session, :null => false
68
+ DateTime :created_at, :null => false
69
+ DateTime :updated_at
70
+ end unless db.table_exists?(@default_options[:table_name])
71
+ end
72
+ end
73
+
74
+ def _put(sid, session)
75
+ if _exists?(sid)
76
+ _record(sid).update :session => [Marshal.dump(session)].pack('m*'), :updated_at => Time.now.utc
77
+ else
78
+ @pool.insert :sid => sid, :session => [Marshal.dump(session)].pack('m*'), :created_at => Time.now.utc
79
+ end
80
+ end
81
+
82
+ def _get(sid)
83
+ if _exists?(sid)
84
+ Marshal.load(_record(sid).first[:session].unpack('m*').first)
85
+ end
86
+ end
87
+
88
+ def _delete(sid)
89
+ if _exists?(sid)
90
+ _record(sid).delete
91
+ end
92
+ end
93
+
94
+ def _exists?(sid)
95
+ !_record(sid).empty?
96
+ end
97
+
98
+ def _record(sid)
99
+ @pool.filter('sid = ?', sid)
100
+ end
101
+ end
102
+ end
103
+ end
104
+
@@ -0,0 +1,99 @@
1
+ require 'rack/session/abstract/id'
2
+ require 'sequel'
3
+
4
+ module Rack
5
+ module Session
6
+ class Sequel < Abstract::ID
7
+ attr_reader :mutex, :pool
8
+
9
+ DEFAULT_OPTIONS = Abstract::ID::DEFAULT_OPTIONS.merge \
10
+ :table_name => :sessions, :db_uri => 'sqlite:/'
11
+
12
+ def initialize(app, options={})
13
+ options = {:db => options } if options.kind_of? ::Sequel::Database
14
+ options = {:db_uri => options } if options.is_a? String
15
+ super
16
+ db = @default_options[:db] || ::Sequel.connect(@default_options[:db_uri])
17
+ db.create_table @default_options[:table_name] do
18
+ primary_key :id
19
+ String :sid, :unique => true, :null => false
20
+ text :session
21
+ DateTime :updated_at
22
+ end unless db.table_exists?(@default_options[:table_name])
23
+ @pool = db[@default_options[:table_name]]
24
+ @mutex = Mutex.new
25
+ end
26
+
27
+ def generate_sid
28
+ loop do
29
+ sid = super
30
+ break sid unless _exists? sid
31
+ end
32
+ end
33
+
34
+ def get_session(env, sid)
35
+ with_lock(env, [nil, {}]) do
36
+ unless sid and session = _get(sid)
37
+ sid, session = generate_sid, {}
38
+ _put sid, session
39
+ end
40
+ [sid, session]
41
+ end
42
+ end
43
+
44
+ def set_session(env, session_id, new_session, options)
45
+ with_lock(env, false) do
46
+ _put session_id, new_session
47
+ session_id
48
+ end
49
+ end
50
+
51
+ def destroy_session(env, session_id, options)
52
+ with_lock(env) do
53
+ _delete(session_id)
54
+ generate_sid unless options[:drop]
55
+ end
56
+ end
57
+
58
+ def with_lock(env, default=nil)
59
+ @mutex.lock if env['rack.multithread']
60
+ yield
61
+ rescue
62
+ default
63
+ ensure
64
+ @mutex.unlock if @mutex.locked?
65
+ end
66
+
67
+ private
68
+
69
+ def _put(sid, session)
70
+ if _exists?(sid)
71
+ _record(sid).update :session => [Marshal.dump(session)].pack('m*'), :updated_at => Time.now.utc
72
+ else
73
+ @pool.insert :sid => sid, :session => [Marshal.dump(session)].pack('m*'), :updated_at => Time.now.utc
74
+ end
75
+ end
76
+
77
+ def _get(sid)
78
+ if _exists?(sid)
79
+ Marshal.load(_record(sid).first[:session].unpack('m*').first)
80
+ end
81
+ end
82
+
83
+ def _delete(sid)
84
+ if _exists?(sid)
85
+ _record(sid).delete
86
+ end
87
+ end
88
+
89
+ def _exists?(sid)
90
+ !_record(sid).empty?
91
+ end
92
+
93
+ def _record(sid)
94
+ @pool.filter('sid = ?', sid)
95
+ end
96
+ end
97
+ end
98
+ end
99
+
@@ -0,0 +1,24 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "rack-session-sequel/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "rack-session-sequel"
7
+ s.version = Rack::Session::Sequel::VERSION
8
+ s.authors = ["Masato Igarashi"]
9
+ s.email = ["m@igrs.jp"]
10
+ s.homepage = "http://github.com/migrs/rack-session-sequel"
11
+ s.summary = %q{Rack session store for Sequel}
12
+ s.description = %q{Rack session store for Sequel}
13
+
14
+ #s.rubyforge_project = "rack-session-sequel"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ s.add_development_dependency "bacon"
22
+ s.add_runtime_dependency "rack"
23
+ s.add_runtime_dependency "sequel"
24
+ end
@@ -0,0 +1,207 @@
1
+ require 'thread'
2
+ require 'rack/mock'
3
+ require 'rack/session/sequel'
4
+ require 'fileutils'
5
+
6
+ describe Rack::Session::Sequel do
7
+ session_key = Rack::Session::Sequel::DEFAULT_OPTIONS[:key]
8
+ session_match = /#{session_key}=[0-9a-fA-F]+;/
9
+
10
+ incrementor = lambda do |env|
11
+ env["rack.session"]["counter"] ||= 0
12
+ env["rack.session"]["counter"] += 1
13
+ Rack::Response.new(env["rack.session"].inspect).to_a
14
+ end
15
+
16
+ session_id = lambda do |env|
17
+ Rack::Response.new(env["rack.session"].inspect).to_a
18
+ end
19
+
20
+ nothing = lambda do |env|
21
+ Rack::Response.new("Nothing").to_a
22
+ end
23
+
24
+ drop_session = lambda do |env|
25
+ env['rack.session.options'][:drop] = true
26
+ incrementor.call(env)
27
+ end
28
+
29
+ renew_session = lambda do |env|
30
+ env['rack.session.options'][:renew] = true
31
+ incrementor.call(env)
32
+ end
33
+
34
+ defer_session = lambda do |env|
35
+ env['rack.session.options'][:defer] = true
36
+ incrementor.call(env)
37
+ end
38
+
39
+ it "creates a new cookie" do
40
+ pool = Rack::Session::Sequel.new(incrementor)
41
+ res = Rack::MockRequest.new(pool).get("/")
42
+ res["Set-Cookie"].should.match session_match
43
+ res.body.should.equal '{"counter"=>1}'
44
+ end
45
+
46
+ it "determines session from a cookie" do
47
+ pool = Rack::Session::Sequel.new(incrementor)
48
+ req = Rack::MockRequest.new(pool)
49
+ cookie = req.get("/")["Set-Cookie"]
50
+ req.get("/", "HTTP_COOKIE" => cookie).
51
+ body.should.equal '{"counter"=>2}'
52
+ req.get("/", "HTTP_COOKIE" => cookie).
53
+ body.should.equal '{"counter"=>3}'
54
+ end
55
+
56
+ it "survives nonexistant cookies" do
57
+ pool = Rack::Session::Sequel.new(incrementor)
58
+ res = Rack::MockRequest.new(pool).
59
+ get("/", "HTTP_COOKIE" => "#{session_key}=blarghfasel")
60
+ res.body.should.equal '{"counter"=>1}'
61
+ end
62
+
63
+ it "does not send the same session id if it did not change" do
64
+ pool = Rack::Session::Sequel.new(incrementor)
65
+ req = Rack::MockRequest.new(pool)
66
+
67
+ res0 = req.get("/")
68
+ cookie = res0["Set-Cookie"][session_match]
69
+ res0.body.should.equal '{"counter"=>1}'
70
+ pool.pool.count.should.equal 1
71
+
72
+ res1 = req.get("/", "HTTP_COOKIE" => cookie)
73
+ res1["Set-Cookie"].should.be.nil
74
+ res1.body.should.equal '{"counter"=>2}'
75
+ pool.pool.count.should.equal 1
76
+
77
+ res2 = req.get("/", "HTTP_COOKIE" => cookie)
78
+ res2["Set-Cookie"].should.be.nil
79
+ res2.body.should.equal '{"counter"=>3}'
80
+ pool.pool.count.should.equal 1
81
+ end
82
+
83
+ it "deletes cookies with :drop option" do
84
+ pool = Rack::Session::Sequel.new(incrementor)
85
+ req = Rack::MockRequest.new(pool)
86
+ drop = Rack::Utils::Context.new(pool, drop_session)
87
+ dreq = Rack::MockRequest.new(drop)
88
+
89
+ res1 = req.get("/")
90
+ session = (cookie = res1["Set-Cookie"])[session_match]
91
+ res1.body.should.equal '{"counter"=>1}'
92
+ pool.pool.count.should.equal 1
93
+
94
+ res2 = dreq.get("/", "HTTP_COOKIE" => cookie)
95
+ res2["Set-Cookie"].should.be.nil
96
+ res2.body.should.equal '{"counter"=>2}'
97
+ pool.pool.count.should.equal 0
98
+
99
+ res3 = req.get("/", "HTTP_COOKIE" => cookie)
100
+ res3["Set-Cookie"][session_match].should.not.equal session
101
+ res3.body.should.equal '{"counter"=>1}'
102
+ pool.pool.count.should.equal 1
103
+ end
104
+
105
+ it "provides new session id with :renew option" do
106
+ pool = Rack::Session::Sequel.new(incrementor)
107
+ req = Rack::MockRequest.new(pool)
108
+ renew = Rack::Utils::Context.new(pool, renew_session)
109
+ rreq = Rack::MockRequest.new(renew)
110
+
111
+ res1 = req.get("/")
112
+ session = (cookie = res1["Set-Cookie"])[session_match]
113
+ res1.body.should.equal '{"counter"=>1}'
114
+ pool.pool.count.should.equal 1
115
+
116
+ res2 = rreq.get("/", "HTTP_COOKIE" => cookie)
117
+ new_cookie = res2["Set-Cookie"]
118
+ new_session = new_cookie[session_match]
119
+ new_session.should.not.equal session
120
+ res2.body.should.equal '{"counter"=>2}'
121
+ pool.pool.count.should.equal 1
122
+
123
+ res3 = req.get("/", "HTTP_COOKIE" => new_cookie)
124
+ res3.body.should.equal '{"counter"=>3}'
125
+ pool.pool.count.should.equal 1
126
+
127
+ res4 = req.get("/", "HTTP_COOKIE" => cookie)
128
+ res4.body.should.equal '{"counter"=>1}'
129
+ pool.pool.count.should.equal 2
130
+ end
131
+
132
+ it "omits cookie with :defer option" do
133
+ pool = Rack::Session::Sequel.new(incrementor)
134
+ defer = Rack::Utils::Context.new(pool, defer_session)
135
+ dreq = Rack::MockRequest.new(defer)
136
+
137
+ res1 = dreq.get("/")
138
+ res1["Set-Cookie"].should.equal nil
139
+ res1.body.should.equal '{"counter"=>1}'
140
+ pool.pool.count.should.equal 1
141
+ end
142
+
143
+ # anyone know how to do this better?
144
+ it "should merge sessions when multithreaded" do
145
+ unless $DEBUG
146
+ 1.should.equal 1
147
+ next
148
+ end
149
+
150
+ warn 'Running multithread tests for Session::Sequel'
151
+ pool = Rack::Session::Sequel.new(incrementor)
152
+ req = Rack::MockRequest.new(pool)
153
+
154
+ res = req.get('/')
155
+ res.body.should.equal '{"counter"=>1}'
156
+ cookie = res["Set-Cookie"]
157
+ sess_id = cookie[/#{pool.key}=([^,;]+)/,1]
158
+
159
+ delta_incrementor = lambda do |env|
160
+ # emulate disconjoinment of threading
161
+ env['rack.session'] = env['rack.session'].dup
162
+ Thread.stop
163
+ env['rack.session'][(Time.now.usec*rand).to_i] = true
164
+ incrementor.call(env)
165
+ end
166
+ tses = Rack::Utils::Context.new pool, delta_incrementor
167
+ treq = Rack::MockRequest.new(tses)
168
+ tnum = rand(7).to_i+5
169
+ r = Array.new(tnum) do
170
+ Thread.new(treq) do |run|
171
+ run.get('/', "HTTP_COOKIE" => cookie, 'rack.multithread' => true)
172
+ end
173
+ end.reverse.map{|t| t.run.join.value }
174
+ r.each do |resp|
175
+ resp['Set-Cookie'].should.equal cookie
176
+ resp.body.should.include '"counter"=>2'
177
+ end
178
+
179
+ session = pool.pool[sess_id]
180
+ session.count.should.equal tnum+1 # counter
181
+ session['counter'].should.equal 2 # meeeh
182
+ end
183
+
184
+ it "does not return a cookie if cookie was not read/written" do
185
+ app = Rack::Session::Sequel.new(nothing)
186
+ res = Rack::MockRequest.new(app).get("/")
187
+ res["Set-Cookie"].should.be.nil
188
+ end
189
+
190
+ it "does not return a cookie if cookie was not written (only read)" do
191
+ app = Rack::Session::Sequel.new(session_id)
192
+ res = Rack::MockRequest.new(app).get("/")
193
+ res["Set-Cookie"].should.be.nil
194
+ end
195
+
196
+ it "returns even if not read/written if :expire_after is set" do
197
+ app = Rack::Session::Sequel.new(nothing, :expire_after => 3600)
198
+ res = Rack::MockRequest.new(app).get("/", 'rack.session' => {'not' => 'empty'})
199
+ res["Set-Cookie"].should.not.be.nil
200
+ end
201
+
202
+ it "returns no cookie if no data was written and no session was created previously, even if :expire_after is set" do
203
+ app = Rack::Session::Sequel.new(nothing, :expire_after => 3600)
204
+ res = Rack::MockRequest.new(app).get("/")
205
+ res["Set-Cookie"].should.be.nil
206
+ end
207
+ end
metadata ADDED
@@ -0,0 +1,91 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rack-session-sequel
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Masato Igarashi
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-01-29 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: bacon
16
+ requirement: &70106969290620 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: *70106969290620
25
+ - !ruby/object:Gem::Dependency
26
+ name: rack
27
+ requirement: &70106962349140 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *70106962349140
36
+ - !ruby/object:Gem::Dependency
37
+ name: sequel
38
+ requirement: &70106962348640 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ type: :runtime
45
+ prerelease: false
46
+ version_requirements: *70106962348640
47
+ description: Rack session store for Sequel
48
+ email:
49
+ - m@igrs.jp
50
+ executables: []
51
+ extensions: []
52
+ extra_rdoc_files: []
53
+ files:
54
+ - .gitignore
55
+ - .travis.yml
56
+ - CHANGES.md
57
+ - Gemfile
58
+ - README.md
59
+ - Rakefile
60
+ - lib/rack-session-sequel.rb
61
+ - lib/rack-session-sequel/version.rb
62
+ - lib/rack/session/sequel.rb
63
+ - lib/rack/session/sequel_org.rb
64
+ - rack-session-sequel.gemspec
65
+ - spec/spec_session_sequel.rb
66
+ homepage: http://github.com/migrs/rack-session-sequel
67
+ licenses: []
68
+ post_install_message:
69
+ rdoc_options: []
70
+ require_paths:
71
+ - lib
72
+ required_ruby_version: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ required_rubygems_version: !ruby/object:Gem::Requirement
79
+ none: false
80
+ requirements:
81
+ - - ! '>='
82
+ - !ruby/object:Gem::Version
83
+ version: '0'
84
+ requirements: []
85
+ rubyforge_project:
86
+ rubygems_version: 1.8.10
87
+ signing_key:
88
+ specification_version: 3
89
+ summary: Rack session store for Sequel
90
+ test_files:
91
+ - spec/spec_session_sequel.rb