rack-session-sequel 0.0.1

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.
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