sequel-sessions 1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +2 -0
- data/LICENSE +7 -0
- data/README.md +28 -0
- data/lib/rack/session/sequel.rb +74 -0
- data/lib/rack/session/version.rb +7 -0
- data/lib/sequel-sessions.rb +5 -0
- data/sequel-sessions.gemspec +20 -0
- data/spec/sequel_sessions_spec.rb +217 -0
- metadata +108 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 4201628cc2942c6e826292a9821a3e7e74d25eb6
|
4
|
+
data.tar.gz: 439063bfe5bbfa96d88d2285b2fc6e637925101e
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 073f31a44929aa4a89784c2bd37fe261013be2ac0d456a0a656db1c83ce38bc1ed56339409bfc63feec9373dfb1410f9dda8dc866877c43b22e94b97f9b9d070
|
7
|
+
data.tar.gz: 759b68e936ba516e84adabfb3038dfbec45fc8e7aa9c96ed89c320aa2a784411ba7a556d31c159ddf9aa25556704b47dd26fc125f667d158f48f2069b30ce250
|
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
Copyright 2017 Tom Wardrop
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
4
|
+
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
6
|
+
|
7
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
[sequel-sessions]
|
2
|
+
=================
|
3
|
+
|
4
|
+
Sequel-based session middleware for Rack 2.0 and above.
|
5
|
+
|
6
|
+
https://github.com/wardrop/sequel-sessions
|
7
|
+
|
8
|
+
## Installation
|
9
|
+
|
10
|
+
gem install sequel-sessions
|
11
|
+
|
12
|
+
## Usage
|
13
|
+
|
14
|
+
Simplest example using in-memory SQLite database:
|
15
|
+
|
16
|
+
```ruby
|
17
|
+
use Rack::Session::Sequel, Sequel.sqlite
|
18
|
+
```
|
19
|
+
|
20
|
+
A more fleshed out example with multiple options:
|
21
|
+
|
22
|
+
```ruby
|
23
|
+
db_connection = Sequel.connect(adapter: 'mysql', host: 'localhost', username: 'root', database: 'blog')
|
24
|
+
use Rack::Session::Sequel, db: db_connection, table_name: :user_sessions, :expire_after => 3600
|
25
|
+
```
|
26
|
+
|
27
|
+
## License
|
28
|
+
Copyright © 2017 Tom Wardrop. Distributed under the [MIT license](http://www.opensource.org/licenses/mit-license).
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module Rack
|
2
|
+
module Session
|
3
|
+
class Sequel < Abstract::Persisted
|
4
|
+
|
5
|
+
DEFAULT_OPTIONS = Abstract::Persisted::DEFAULT_OPTIONS.merge(:table_name => :sessions)
|
6
|
+
|
7
|
+
# Options arguments can be either a Sequel::Database instance, or a hash. Options beyond those defined in
|
8
|
+
# Rack::Session::Abstract::Persisted include:
|
9
|
+
# :db => The a Sequel database object.
|
10
|
+
# :table_name => The name of the table to store session data. Defaults to :sessions.
|
11
|
+
def initialize(app, options={})
|
12
|
+
options = {:db => options } if options.is_a? ::Sequel::Database
|
13
|
+
super
|
14
|
+
@mutex = Mutex.new
|
15
|
+
setup_database
|
16
|
+
end
|
17
|
+
|
18
|
+
def find_session(env, sid)
|
19
|
+
record = table.filter(sid: sid).first
|
20
|
+
if record
|
21
|
+
session = Marshal.load(record[:session].unpack('m*').first)
|
22
|
+
else
|
23
|
+
sid, session = generate_sid, {}
|
24
|
+
end
|
25
|
+
[sid, session]
|
26
|
+
end
|
27
|
+
|
28
|
+
def write_session(req, sid, session, options)
|
29
|
+
with_lock(req) do
|
30
|
+
begin
|
31
|
+
table.insert(sid: sid, session: [Marshal.dump(session)].pack('m*'), :created_at => Time.now.utc)
|
32
|
+
rescue ::Sequel::UniqueConstraintViolation
|
33
|
+
table.filter(sid: sid).update(sid: sid, session: [Marshal.dump(session)].pack('m*'), :updated_at => Time.now.utc)
|
34
|
+
end
|
35
|
+
sid
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def delete_session(req, sid, options)
|
40
|
+
with_lock(req) do
|
41
|
+
table.filter(sid: sid).delete
|
42
|
+
generate_sid unless options[:drop]
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def db
|
47
|
+
@default_options[:db]
|
48
|
+
end
|
49
|
+
|
50
|
+
def table
|
51
|
+
db[@default_options[:table_name].to_sym]
|
52
|
+
end
|
53
|
+
|
54
|
+
protected
|
55
|
+
|
56
|
+
def with_lock(req)
|
57
|
+
@mutex.lock if req.env['rack.multithread']
|
58
|
+
yield
|
59
|
+
ensure
|
60
|
+
@mutex.unlock if @mutex.locked?
|
61
|
+
end
|
62
|
+
|
63
|
+
def setup_database
|
64
|
+
db.create_table(@default_options[:table_name]) do
|
65
|
+
String :sid, :unique => true, :null => false, :primary_key => true
|
66
|
+
text :session, :null => false
|
67
|
+
DateTime :created_at, :null => false
|
68
|
+
DateTime :updated_at
|
69
|
+
end unless db.table_exists? @default_options[:table_name]
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
$LOAD_PATH.unshift File.expand_path('../lib', __FILE__)
|
2
|
+
require 'rack/session/version'
|
3
|
+
|
4
|
+
Gem::Specification.new 'sequel-sessions', Rack::Session::Sequel::VERSION do |s|
|
5
|
+
s.summary = 'Sequel-based session middleware for Rack 2.0+'
|
6
|
+
s.description = 'Sequel-based session middleware for Rack 2.0+'
|
7
|
+
s.authors = ['Tom Wardrop']
|
8
|
+
s.email = 'tom@tomwardrop.com'
|
9
|
+
s.homepage = 'https://github.com/wardrop/sequel-sessions'
|
10
|
+
s.license = 'MIT'
|
11
|
+
s.files = Dir.glob(`git ls-files`.split("\n") - %w[.gitignore])
|
12
|
+
s.test_files = Dir.glob('spec/**/*_spec.rb')
|
13
|
+
|
14
|
+
s.required_ruby_version = '>= 2.0.0'
|
15
|
+
|
16
|
+
s.add_runtime_dependency "rack", '>= 2.0'
|
17
|
+
s.add_runtime_dependency "sequel", '>= 4.0'
|
18
|
+
s.add_development_dependency "sqlite3", '>= 1.3'
|
19
|
+
s.add_development_dependency "minitest", '>= 5.1'
|
20
|
+
end
|
@@ -0,0 +1,217 @@
|
|
1
|
+
$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
|
2
|
+
|
3
|
+
# Based on Rack's test/spec_session_pool.rb.
|
4
|
+
require 'minitest/autorun'
|
5
|
+
require 'thread'
|
6
|
+
require 'rack/lint'
|
7
|
+
require 'rack/mock'
|
8
|
+
require 'sequel-sessions'
|
9
|
+
|
10
|
+
describe Rack::Session::Sequel do
|
11
|
+
|
12
|
+
session_key = Rack::Session::Pool::DEFAULT_OPTIONS[:key]
|
13
|
+
session_match = /#{session_key}=[0-9a-fA-F]+;/
|
14
|
+
|
15
|
+
let(:db) { Sequel.sqlite }
|
16
|
+
|
17
|
+
incrementor = lambda do |env|
|
18
|
+
env["rack.session"]["counter"] ||= 0
|
19
|
+
env["rack.session"]["counter"] += 1
|
20
|
+
Rack::Response.new(env["rack.session"].inspect).to_a
|
21
|
+
end
|
22
|
+
|
23
|
+
session_id = Rack::Lint.new(lambda do |env|
|
24
|
+
Rack::Response.new(env["rack.session"].inspect).to_a
|
25
|
+
end)
|
26
|
+
|
27
|
+
nothing = Rack::Lint.new(lambda do |env|
|
28
|
+
Rack::Response.new("Nothing").to_a
|
29
|
+
end)
|
30
|
+
|
31
|
+
drop_session = Rack::Lint.new(lambda do |env|
|
32
|
+
env['rack.session.options'][:drop] = true
|
33
|
+
incrementor.call(env)
|
34
|
+
end)
|
35
|
+
|
36
|
+
renew_session = Rack::Lint.new(lambda do |env|
|
37
|
+
env['rack.session.options'][:renew] = true
|
38
|
+
incrementor.call(env)
|
39
|
+
end)
|
40
|
+
|
41
|
+
defer_session = Rack::Lint.new(lambda do |env|
|
42
|
+
env['rack.session.options'][:defer] = true
|
43
|
+
incrementor.call(env)
|
44
|
+
end)
|
45
|
+
|
46
|
+
incrementor = Rack::Lint.new(incrementor)
|
47
|
+
|
48
|
+
it "creates a new cookie" do
|
49
|
+
pool = Rack::Session::Sequel.new(incrementor, db)
|
50
|
+
res = Rack::MockRequest.new(pool).get("/")
|
51
|
+
res["Set-Cookie"].must_include "#{session_key}="
|
52
|
+
res.body.must_equal '{"counter"=>1}'
|
53
|
+
end
|
54
|
+
|
55
|
+
it "determines session from a cookie" do
|
56
|
+
middleware = Rack::Session::Sequel.new(incrementor, db)
|
57
|
+
req = Rack::MockRequest.new(middleware)
|
58
|
+
cookie = req.get("/")["Set-Cookie"]
|
59
|
+
req.get("/", "HTTP_COOKIE" => cookie).
|
60
|
+
body.must_equal '{"counter"=>2}'
|
61
|
+
req.get("/", "HTTP_COOKIE" => cookie).
|
62
|
+
body.must_equal '{"counter"=>3}'
|
63
|
+
end
|
64
|
+
|
65
|
+
it "survives nonexistant cookies" do
|
66
|
+
pool = Rack::Session::Sequel.new(incrementor, db)
|
67
|
+
res = Rack::MockRequest.new(pool).
|
68
|
+
get("/", "HTTP_COOKIE" => "#{session_key}=blarghfasel")
|
69
|
+
res.body.must_equal '{"counter"=>1}'
|
70
|
+
end
|
71
|
+
|
72
|
+
it "does not send the same session id if it did not change" do
|
73
|
+
pool = Rack::Session::Sequel.new(incrementor, db)
|
74
|
+
req = Rack::MockRequest.new(pool)
|
75
|
+
|
76
|
+
res0 = req.get("/")
|
77
|
+
cookie = res0["Set-Cookie"][session_match]
|
78
|
+
res0.body.must_equal '{"counter"=>1}'
|
79
|
+
pool.table.count.must_equal 1
|
80
|
+
|
81
|
+
res1 = req.get("/", "HTTP_COOKIE" => cookie)
|
82
|
+
res1["Set-Cookie"].must_be_nil
|
83
|
+
res1.body.must_equal '{"counter"=>2}'
|
84
|
+
pool.table.count.must_equal 1
|
85
|
+
|
86
|
+
res2 = req.get("/", "HTTP_COOKIE" => cookie)
|
87
|
+
res2["Set-Cookie"].must_be_nil
|
88
|
+
res2.body.must_equal '{"counter"=>3}'
|
89
|
+
pool.table.count.must_equal 1
|
90
|
+
end
|
91
|
+
|
92
|
+
it "deletes cookies with :drop option" do
|
93
|
+
pool = Rack::Session::Sequel.new(incrementor, db)
|
94
|
+
req = Rack::MockRequest.new(pool)
|
95
|
+
drop = Rack::Utils::Context.new(pool, drop_session)
|
96
|
+
dreq = Rack::MockRequest.new(drop)
|
97
|
+
|
98
|
+
res1 = req.get("/")
|
99
|
+
session = (cookie = res1["Set-Cookie"])[session_match]
|
100
|
+
res1.body.must_equal '{"counter"=>1}'
|
101
|
+
pool.table.count.must_equal 1
|
102
|
+
|
103
|
+
res2 = dreq.get("/", "HTTP_COOKIE" => cookie)
|
104
|
+
res2["Set-Cookie"].must_be_nil
|
105
|
+
res2.body.must_equal '{"counter"=>2}'
|
106
|
+
pool.table.count.must_equal 0
|
107
|
+
|
108
|
+
res3 = req.get("/", "HTTP_COOKIE" => cookie)
|
109
|
+
res3["Set-Cookie"][session_match].wont_equal session
|
110
|
+
res3.body.must_equal '{"counter"=>1}'
|
111
|
+
pool.table.count.must_equal 1
|
112
|
+
end
|
113
|
+
|
114
|
+
it "provides new session id with :renew option" do
|
115
|
+
pool = Rack::Session::Sequel.new(incrementor, db)
|
116
|
+
req = Rack::MockRequest.new(pool)
|
117
|
+
renew = Rack::Utils::Context.new(pool, renew_session)
|
118
|
+
rreq = Rack::MockRequest.new(renew)
|
119
|
+
|
120
|
+
res1 = req.get("/")
|
121
|
+
session = (cookie = res1["Set-Cookie"])[session_match]
|
122
|
+
res1.body.must_equal '{"counter"=>1}'
|
123
|
+
pool.table.count.must_equal 1
|
124
|
+
|
125
|
+
res2 = rreq.get("/", "HTTP_COOKIE" => cookie)
|
126
|
+
new_cookie = res2["Set-Cookie"]
|
127
|
+
new_session = new_cookie[session_match]
|
128
|
+
new_session.wont_equal session
|
129
|
+
res2.body.must_equal '{"counter"=>2}'
|
130
|
+
pool.table.count.must_equal 1
|
131
|
+
|
132
|
+
res3 = req.get("/", "HTTP_COOKIE" => new_cookie)
|
133
|
+
res3.body.must_equal '{"counter"=>3}'
|
134
|
+
pool.table.count.must_equal 1
|
135
|
+
|
136
|
+
res4 = req.get("/", "HTTP_COOKIE" => cookie)
|
137
|
+
res4.body.must_equal '{"counter"=>1}'
|
138
|
+
pool.table.count.must_equal 2
|
139
|
+
end
|
140
|
+
|
141
|
+
it "omits cookie with :defer option" do
|
142
|
+
pool = Rack::Session::Sequel.new(incrementor, db)
|
143
|
+
defer = Rack::Utils::Context.new(pool, defer_session)
|
144
|
+
dreq = Rack::MockRequest.new(defer)
|
145
|
+
|
146
|
+
res1 = dreq.get("/")
|
147
|
+
res1["Set-Cookie"].must_be_nil
|
148
|
+
res1.body.must_equal '{"counter"=>1}'
|
149
|
+
pool.table.count.must_equal 1
|
150
|
+
end
|
151
|
+
|
152
|
+
# anyone know how to do this better?
|
153
|
+
it "should merge sessions when multithreaded" do
|
154
|
+
unless $DEBUG
|
155
|
+
1.must_equal 1
|
156
|
+
next
|
157
|
+
end
|
158
|
+
|
159
|
+
warn 'Running multithread tests for Session::Pool'
|
160
|
+
pool = Rack::Session::Sequel.new(incrementor, db)
|
161
|
+
req = Rack::MockRequest.new(pool)
|
162
|
+
|
163
|
+
res = req.get('/')
|
164
|
+
res.body.must_equal '{"counter"=>1}'
|
165
|
+
cookie = res["Set-Cookie"]
|
166
|
+
sess_id = cookie[/#{pool.key}=([^,;]+)/,1]
|
167
|
+
|
168
|
+
delta_incrementor = lambda do |env|
|
169
|
+
# emulate disconjoinment of threading
|
170
|
+
env['rack.session'] = env['rack.session'].dup
|
171
|
+
Thread.stop
|
172
|
+
env['rack.session'][(Time.now.usec*rand).to_i] = true
|
173
|
+
incrementor.call(env)
|
174
|
+
end
|
175
|
+
tses = Rack::Utils::Context.new pool, delta_incrementor
|
176
|
+
treq = Rack::MockRequest.new(tses)
|
177
|
+
tnum = rand(7).to_i+5
|
178
|
+
r = Array.new(tnum) do
|
179
|
+
Thread.new(treq) do |run|
|
180
|
+
run.get('/', "HTTP_COOKIE" => cookie, 'rack.multithread' => true)
|
181
|
+
end
|
182
|
+
end.reverse.map{|t| t.run.join.value }
|
183
|
+
r.each do |resp|
|
184
|
+
resp['Set-Cookie'].must_equal cookie
|
185
|
+
resp.body.must_include '"counter"=>2'
|
186
|
+
end
|
187
|
+
|
188
|
+
session = pool.pool[sess_id]
|
189
|
+
session.size.must_equal tnum+1 # counter
|
190
|
+
session['counter'].must_equal 2 # meeeh
|
191
|
+
end
|
192
|
+
|
193
|
+
it "does not return a cookie if cookie was not read/written" do
|
194
|
+
app = Rack::Session::Sequel.new(nothing, db)
|
195
|
+
res = Rack::MockRequest.new(app).get("/")
|
196
|
+
res["Set-Cookie"].must_be_nil
|
197
|
+
end
|
198
|
+
|
199
|
+
it "does not return a cookie if cookie was not written (only read)" do
|
200
|
+
app = Rack::Session::Sequel.new(session_id, db)
|
201
|
+
res = Rack::MockRequest.new(app).get("/")
|
202
|
+
res["Set-Cookie"].must_be_nil
|
203
|
+
end
|
204
|
+
|
205
|
+
it "returns even if not read/written if :expire_after is set" do
|
206
|
+
app = Rack::Session::Sequel.new(nothing, :expire_after => 3600, :db => db)
|
207
|
+
res = Rack::MockRequest.new(app).get("/", 'rack.session' => {'not' => 'empty'})
|
208
|
+
res["Set-Cookie"].wont_be :nil?
|
209
|
+
end
|
210
|
+
|
211
|
+
it "returns no cookie if no data was written and no session was created previously, even if :expire_after is set" do
|
212
|
+
app = Rack::Session::Sequel.new(nothing, :expire_after => 3600, :db => db)
|
213
|
+
res = Rack::MockRequest.new(app).get("/")
|
214
|
+
res["Set-Cookie"].must_be_nil
|
215
|
+
end
|
216
|
+
|
217
|
+
end
|
metadata
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: sequel-sessions
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: '1.0'
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Tom Wardrop
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-06-16 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rack
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '2.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: sequel
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '4.0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '4.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: sqlite3
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.3'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.3'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: minitest
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '5.1'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '5.1'
|
69
|
+
description: Sequel-based session middleware for Rack 2.0+
|
70
|
+
email: tom@tomwardrop.com
|
71
|
+
executables: []
|
72
|
+
extensions: []
|
73
|
+
extra_rdoc_files: []
|
74
|
+
files:
|
75
|
+
- Gemfile
|
76
|
+
- LICENSE
|
77
|
+
- README.md
|
78
|
+
- lib/rack/session/sequel.rb
|
79
|
+
- lib/rack/session/version.rb
|
80
|
+
- lib/sequel-sessions.rb
|
81
|
+
- sequel-sessions.gemspec
|
82
|
+
- spec/sequel_sessions_spec.rb
|
83
|
+
homepage: https://github.com/wardrop/sequel-sessions
|
84
|
+
licenses:
|
85
|
+
- MIT
|
86
|
+
metadata: {}
|
87
|
+
post_install_message:
|
88
|
+
rdoc_options: []
|
89
|
+
require_paths:
|
90
|
+
- lib
|
91
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - ">="
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: 2.0.0
|
96
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
97
|
+
requirements:
|
98
|
+
- - ">="
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
version: '0'
|
101
|
+
requirements: []
|
102
|
+
rubyforge_project:
|
103
|
+
rubygems_version: 2.6.12
|
104
|
+
signing_key:
|
105
|
+
specification_version: 4
|
106
|
+
summary: Sequel-based session middleware for Rack 2.0+
|
107
|
+
test_files:
|
108
|
+
- spec/sequel_sessions_spec.rb
|