mongo-store 0.1.0

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/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,22 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ doc
19
+ .yardoc
20
+ pkg
21
+
22
+ ## PROJECT::SPECIFIC
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 Jonathan Rudenberg
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,24 @@
1
+ = mongo-store
2
+
3
+ Rack session store for MongoDB. See the docs[http://rdoc.info/projects/titanous/mongo-store] for more usage details.
4
+
5
+ == Installation
6
+ gem install mongo-store
7
+
8
+ == Usage
9
+ use Rack::Session::Mongo, :connection => @existing_mongodb_connection,
10
+ :expire_after => 1800
11
+
12
+ == Note on Patches/Pull Requests
13
+
14
+ * Fork the project.
15
+ * Make your feature addition or bug fix.
16
+ * Add tests for it. This is important so I don't break it in a
17
+ future version unintentionally.
18
+ * Commit, do not mess with rakefile, version, or history.
19
+ (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
20
+ * Send me a pull request. Bonus points for topic branches.
21
+
22
+ == Copyright
23
+
24
+ Copyright (c) 2010 Jonathan Rudenberg. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,38 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'jeweler'
4
+ require 'spec/rake/spectask'
5
+ require 'yard'
6
+
7
+ Jeweler::Tasks.new do |gem|
8
+ gem.name = 'mongo-store'
9
+ gem.summary = 'Rack session store for MongoDB'
10
+ gem.email = 'jonathan@titanous.com'
11
+ gem.homepage = 'http://github.com/titanous/mongo-store'
12
+ gem.authors = ['Jonathan Rudenberg']
13
+ gem.add_dependency 'mongo'
14
+ gem.add_dependency 'rack', '~> 1.1.0'
15
+ gem.add_development_dependency 'rspec', '>= 1.2.9'
16
+ gem.add_development_dependency 'yard', '>= 0'
17
+ end
18
+
19
+ Jeweler::GemcutterTasks.new
20
+
21
+ Spec::Rake::SpecTask.new(:spec) do |spec|
22
+ spec.libs << 'lib' << 'spec'
23
+ spec.spec_files = FileList['spec/**/*_spec.rb']
24
+ end
25
+
26
+ Spec::Rake::SpecTask.new(:rcov) do |spec|
27
+ spec.libs << 'lib' << 'spec'
28
+ spec.pattern = 'spec/**/*_spec.rb'
29
+ spec.rcov = true
30
+ end
31
+
32
+ task :spec => :check_dependencies
33
+
34
+ task :default => :spec
35
+
36
+ YARD::Rake::YardocTask.new(:doc) do |t|
37
+ t.options = ['--legacy'] if RUBY_VERSION < '1.9.0'
38
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,4 @@
1
+ require 'rubygems'
2
+ require 'mongo'
3
+ require 'rack/session/abstract/id'
4
+ require 'rack/session/mongo'
@@ -0,0 +1,147 @@
1
+ require 'rubygems'
2
+ require 'mongo'
3
+ require 'rack/session/abstract/id'
4
+
5
+ module Rack
6
+ module Session
7
+ # Implements the +Rack::Session::Abstract::ID+ session store interface
8
+ #
9
+ # Cookies sent to the client for maintaining sessions will only contain an
10
+ # id reference. See {Rack::Session::Mongo#initialize below} for options.
11
+ #
12
+ # == Usage Example
13
+ #
14
+ # use Rack::Session::Mongo, :connection => @existing_mongodb_connection,
15
+ # :expire_after => 1800
16
+ class Mongo < Abstract::ID
17
+ attr_reader :mutex, :pool, :connection
18
+ DEFAULT_OPTIONS = Abstract::ID::DEFAULT_OPTIONS.merge :db => 'rack', :collection => 'sessions', :drop => false
19
+
20
+ # Creates a new Mongo session store pool. You probably won't initialize
21
+ # it his way. See the overview for standard usage instructions.
22
+ #
23
+ # Unless specified, the options can be set on a per request basis, in the
24
+ # +rack.session.options+ environment hash. Additionally the id of
25
+ # the session can be found within the options hash at the key +:id+. It is
26
+ # highly not recommended to change its value.
27
+ #
28
+ # @param app a Rack application
29
+ # @param [Hash] options configuration for the session pool
30
+ # @option options [Mongo::Connection] :connection (Mongo::Connection.new)
31
+ # used to create the pool. Change this if you already have a connection
32
+ # setup, or want to connect to a server other than +localhost+.
33
+ # — <i>pool instance global</i>
34
+ # @option options [String] :db ('rack') the Mongo db to use — <i>pool
35
+ # instance global</i>
36
+ # @option options [String] :collection ('sessions') the Mongo collection
37
+ # to use. — <i>pool instance global</i>
38
+ # @option options [Integer] :expire_after (nil) the time in seconds for
39
+ # the session to last for. *Example:* If this is set to +1800+, the
40
+ # session will be deleted if the client doesn't make a request within 30
41
+ # minutes of its last request.
42
+ # @option options [true, false] :defer (false) don't set the session
43
+ # cookie for this request.
44
+ # @option options [true, false] :renew (false) causes the generation of
45
+ # a new session id, and migrates the data to id. Overrides +:defer+.
46
+ # @option options [true, false] :drop (false) destroys the current
47
+ # session, and creates a new one.
48
+ # @option options [String] :key ('rack.session') the name of the cookie
49
+ # that stores the session id
50
+ # @option options [String] :path ('/') the cookie path
51
+ # @option options [String] :domain (nil) the cookie domain
52
+ # @option options [true, false] :secure (false) the cookie security flag;
53
+ # tells the client to only send the cookie over HTTPS.
54
+ # @option options [true, false] :httponly (true) the cookie HttpOnly flag;
55
+ # makes the cookie invisible to client-side Javascript.
56
+ def initialize(app, options = {})
57
+ super
58
+ @mutex = Mutex.new
59
+ @connection = @default_options[:connection] || ::Mongo::Connection.new
60
+ @pool = @connection.db(@default_options[:db]).collection(@default_options[:collection])
61
+ @pool.create_index('sid', true)
62
+ end
63
+
64
+ def get_session(env, sid)
65
+ @mutex.lock if env['rack.multithread']
66
+ session = find_session(sid) if sid
67
+ unless sid and session
68
+ env['rack.errors'].puts("Session '#{sid}' not found, initializing...") if $VERBOSE and not sid.nil?
69
+ session = {}
70
+ sid = generate_sid
71
+ save_session(sid)
72
+ end
73
+ session.instance_variable_set('@old', {}.merge(session))
74
+ return [sid, session]
75
+ ensure
76
+ @mutex.unlock if env['rack.multithread']
77
+ end
78
+
79
+ def set_session(env, sid, new_session, options)
80
+ @mutex.lock if env['rack.multithread']
81
+ expires = Time.now + options[:expire_after] if !options[:expire_after].nil?
82
+ session = find_session(sid) || {}
83
+ if options[:renew] or options[:drop]
84
+ delete_session(sid)
85
+ return false if options[:drop]
86
+ sid = generate_sid
87
+ save_session(sid, session, expires)
88
+ end
89
+ old_session = new_session.instance_variable_get('@old') || {}
90
+ session = merge_sessions(sid, old_session, new_session, session)
91
+ save_session(sid, session, expires)
92
+ return sid
93
+ ensure
94
+ @mutex.unlock if env['rack.multithread']
95
+ end
96
+
97
+ private
98
+ def generate_sid
99
+ loop do
100
+ sid = super
101
+ break sid unless find_session(sid)
102
+ end
103
+ end
104
+
105
+ def find_session(sid)
106
+ @pool.remove :expires => {'$lte' => Time.now} # clean out expired sessions
107
+ session = @pool.find_one :sid => sid
108
+ session ? unpack(session['data']) : false
109
+ end
110
+
111
+ def delete_session(sid)
112
+ @pool.remove :sid => sid
113
+ end
114
+
115
+ def save_session(sid, session={}, expires=nil)
116
+ @pool.update({:sid => sid}, {:sid => sid, :data => pack(session), :expires => expires}, :upsert => true)
117
+ end
118
+
119
+ def merge_sessions(sid, old, new, current=nil)
120
+ current ||= {}
121
+ unless Hash === old and Hash === new
122
+ warn 'Bad old or new sessions provided.'
123
+ return current
124
+ end
125
+
126
+ delete = old.keys - new.keys
127
+ warn "//@#{sid}: dropping #{delete*','}" if $DEBUG and not delete.empty?
128
+ delete.each{|k| current.delete k }
129
+
130
+ update = new.keys.select{|k| new[k] != old[k] }
131
+ warn "//@#{sid}: updating #{update*','}" if $DEBUG and not update.empty?
132
+ update.each{|k| current[k] = new[k] }
133
+
134
+ current
135
+ end
136
+
137
+ def pack(data)
138
+ [Marshal.dump(data)].pack("m*")
139
+ end
140
+
141
+ def unpack(packed)
142
+ return nil unless packed
143
+ Marshal.load(packed.unpack("m*").first)
144
+ end
145
+ end
146
+ end
147
+ end
@@ -0,0 +1,238 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+
4
+ describe 'Rack::Session::Mongo' do
5
+ before(:each) do
6
+ @session_key = Rack::Session::Mongo::DEFAULT_OPTIONS[:key]
7
+ @session_match = /#{@session_key}=[0-9a-fA-F]+;/
8
+ @incrementor = lambda do |env|
9
+ env['rack.session']['counter'] ||= 0
10
+ env['rack.session']['counter'] += 1
11
+ Rack::Response.new(env['rack.session'].inspect).to_a
12
+ end
13
+ @drop_session = proc do |env|
14
+ env['rack.session.options'][:drop] = true
15
+ @incrementor.call(env)
16
+ end
17
+ @renew_session = proc do |env|
18
+ env['rack.session.options'][:renew] = true
19
+ @incrementor.call(env)
20
+ end
21
+ @defer_session = proc do |env|
22
+ env['rack.session.options'][:defer] = true
23
+ @incrementor.call(env)
24
+ end
25
+ end
26
+
27
+ it 'should specify connection params' do
28
+ mongo = Rack::Session::Mongo.new(@incrementor,
29
+ :connection => Mongo::Connection.new('localhost'), :db => 'rack-test',
30
+ :collection => 'mongo-test')
31
+ pool = mongo.pool
32
+ connection = mongo.connection
33
+
34
+ connection.host.should == 'localhost'
35
+
36
+ pool.should be_kind_of(Mongo::Collection)
37
+ pool.db.name.should == 'rack-test'
38
+ pool.name.should == 'mongo-test'
39
+ end
40
+
41
+ it 'creates a new cookie' do
42
+ pool = Rack::Session::Mongo.new(@incrementor)
43
+ res = Rack::MockRequest.new(pool).get('/')
44
+ res['Set-Cookie'].should match(/#{@session_key}=/)
45
+ res.body.should == '{"counter"=>1}'
46
+ end
47
+
48
+ it 'determines session from a cookie' do
49
+ pool = Rack::Session::Mongo.new(@incrementor)
50
+ req = Rack::MockRequest.new(pool)
51
+ res = req.get('/')
52
+ cookie = res['Set-Cookie']
53
+ req.get('/', 'HTTP_COOKIE' => cookie).
54
+ body.should == '{"counter"=>2}'
55
+ req.get('/', 'HTTP_COOKIE' => cookie).
56
+ body.should == '{"counter"=>3}'
57
+ end
58
+
59
+ it 'survives nonexistant cookies' do
60
+ bad_cookie = 'rack.session=blarghfasel'
61
+ pool = Rack::Session::Mongo.new(@incrementor)
62
+ res = Rack::MockRequest.new(pool).
63
+ get('/', 'HTTP_COOKIE' => bad_cookie)
64
+ res.body.should == '{"counter"=>1}'
65
+ cookie = res['Set-Cookie'][@session_match]
66
+ cookie.should_not match(/#{bad_cookie}/)
67
+ end
68
+
69
+ it 'should maintain freshness' do
70
+ pool = Rack::Session::Mongo.new(@incrementor, :expire_after => 3)
71
+ res = Rack::MockRequest.new(pool).get('/')
72
+ res.body.should include('"counter"=>1')
73
+ cookie = res['Set-Cookie']
74
+ res = Rack::MockRequest.new(pool).get('/', 'HTTP_COOKIE' => cookie)
75
+ res['Set-Cookie'].should == cookie
76
+ res.body.should include('"counter"=>2')
77
+ puts 'Sleeping to expire session' if $DEBUG
78
+ sleep 4
79
+ res = Rack::MockRequest.new(pool).get('/', 'HTTP_COOKIE' => cookie)
80
+ res['Set-Cookie'].should_not == cookie
81
+ res.body.should include('"counter"=>1')
82
+ end
83
+
84
+ it 'deletes cookies with :drop option' do
85
+ pool = Rack::Session::Mongo.new(@incrementor)
86
+ req = Rack::MockRequest.new(pool)
87
+ drop = Rack::Utils::Context.new(pool, @drop_session)
88
+ dreq = Rack::MockRequest.new(drop)
89
+
90
+ res0 = req.get('/')
91
+ session = (cookie = res0['Set-Cookie'])[@session_match]
92
+ res0.body.should == '{"counter"=>1}'
93
+
94
+ res1 = req.get('/', 'HTTP_COOKIE' => cookie)
95
+ res1['Set-Cookie'][@session_match].should == session
96
+ res1.body.should == '{"counter"=>2}'
97
+
98
+ res2 = dreq.get('/', 'HTTP_COOKIE' => cookie)
99
+ res2['Set-Cookie'].should be_nil
100
+ res2.body.should == '{"counter"=>3}'
101
+
102
+ res3 = req.get('/', 'HTTP_COOKIE' => cookie)
103
+ res3['Set-Cookie'][@session_match].should_not == session
104
+ res3.body.should == '{"counter"=>1}'
105
+ end
106
+
107
+ it 'provides new session id with :renew option' do
108
+ pool = Rack::Session::Mongo.new(@incrementor)
109
+ req = Rack::MockRequest.new(pool)
110
+ renew = Rack::Utils::Context.new(pool, @renew_session)
111
+ rreq = Rack::MockRequest.new(renew)
112
+
113
+ res0 = req.get('/')
114
+ session = (cookie = res0['Set-Cookie'])[@session_match]
115
+ res0.body.should == '{"counter"=>1}'
116
+
117
+ res1 = req.get('/', 'HTTP_COOKIE' => cookie)
118
+ res1['Set-Cookie'][@session_match].should == session
119
+ res1.body.should == '{"counter"=>2}'
120
+
121
+ res2 = rreq.get('/', 'HTTP_COOKIE' => cookie)
122
+ new_cookie = res2['Set-Cookie']
123
+ new_session = new_cookie[@session_match]
124
+ new_session.should_not == session
125
+ res2.body.should == '{"counter"=>3}'
126
+
127
+ res3 = req.get('/', 'HTTP_COOKIE' => new_cookie)
128
+ res3['Set-Cookie'][@session_match].should == new_session
129
+ res3.body.should == '{"counter"=>4}'
130
+ end
131
+
132
+ specify 'omits cookie with :defer option' do
133
+ pool = Rack::Session::Mongo.new(@incrementor)
134
+ req = Rack::MockRequest.new(pool)
135
+ defer = Rack::Utils::Context.new(pool, @defer_session)
136
+ dreq = Rack::MockRequest.new(defer)
137
+
138
+ res0 = req.get('/')
139
+ session = (cookie = res0['Set-Cookie'])[@session_match]
140
+ res0.body.should == '{"counter"=>1}'
141
+
142
+ res1 = req.get('/', 'HTTP_COOKIE' => cookie)
143
+ res1['Set-Cookie'][@session_match].should == session
144
+ res1.body.should == '{"counter"=>2}'
145
+
146
+ res2 = dreq.get('/', 'HTTP_COOKIE' => cookie)
147
+ res2['Set-Cookie'].should be_nil
148
+ res2.body.should == '{"counter"=>3}'
149
+
150
+ res3 = req.get('/', 'HTTP_COOKIE' => cookie)
151
+ res3['Set-Cookie'][@session_match].should == session
152
+ res3.body.should == '{"counter"=>4}'
153
+ end
154
+
155
+ # anyone know how to do this better?
156
+ specify 'multithread: should cleanly merge sessions' do
157
+ next unless $DEBUG
158
+ warn 'Running multithread test for Session::Mongo'
159
+ pool = Rack::Session::Mongo.new(@incrementor)
160
+ req = Rack::MockRequest.new(pool)
161
+
162
+ res = req.get('/')
163
+ res.body.should == '{"counter"=>1}'
164
+ cookie = res['Set-Cookie']
165
+ sess_id = cookie[/#{pool.key}=([^,;]+)/,1]
166
+
167
+ delta_incrementor = lambda do |env|
168
+ # emulate disconjoinment of threading
169
+ env['rack.session'] = env['rack.session'].dup
170
+ Thread.stop
171
+ env['rack.session'][(Time.now.usec*rand).to_i] = true
172
+ @incrementor.call(env)
173
+ end
174
+ tses = Rack::Utils::Context.new pool, delta_incrementor
175
+ treq = Rack::MockRequest.new(tses)
176
+ tnum = rand(7).to_i+5
177
+ r = Array.new(tnum) do
178
+ Thread.new(treq) do |run|
179
+ run.get('/', 'HTTP_COOKIE' => cookie, 'rack.multithread' => true)
180
+ end
181
+ end.reverse.map{|t| t.run.join.value }
182
+ r.each do |res|
183
+ res['Set-Cookie'].should == cookie
184
+ res.body.should include('"counter"=>2')
185
+ end
186
+
187
+ session = pool.pool.get(sess_id)
188
+ session.size.should == tnum+1 # counter
189
+ session['counter'].should == 2 # meeeh
190
+
191
+ tnum = rand(7).to_i+5
192
+ r = Array.new(tnum) do |i|
193
+ delta_time = proc do |env|
194
+ env['rack.session'][i] = Time.now
195
+ Thread.stop
196
+ env['rack.session'] = env['rack.session'].dup
197
+ env['rack.session'][i] -= Time.now
198
+ @incrementor.call(env)
199
+ end
200
+ app = Rack::Utils::Context.new pool, time_delta
201
+ req = Rack::MockRequest.new app
202
+ Thread.new(req) do |run|
203
+ run.get('/', 'HTTP_COOKIE' => cookie, 'rack.multithread' => true)
204
+ end
205
+ end.reverse.map{|t| t.run.join.value }
206
+ r.each do |res|
207
+ res['Set-Cookie'].should == cookie
208
+ res.body.should include('"counter"=>3')
209
+ end
210
+
211
+ session = pool.pool.get(sess_id)
212
+ session.size.should == tnum+1
213
+ session['counter'].should == 3
214
+
215
+ drop_counter = proc do |env|
216
+ env['rack.session'].delete 'counter'
217
+ env['rack.session']['foo'] = 'bar'
218
+ [200, {'Content-Type'=>'text/plain'}, env['rack.session'].inspect]
219
+ end
220
+ tses = Rack::Utils::Context.new pool, drop_counter
221
+ treq = Rack::MockRequest.new(tses)
222
+ tnum = rand(7).to_i+5
223
+ r = Array.new(tnum) do
224
+ Thread.new(treq) do |run|
225
+ run.get('/', 'HTTP_COOKIE' => cookie, 'rack.multithread' => true)
226
+ end
227
+ end.reverse.map{|t| t.run.join.value }
228
+ r.each do |res|
229
+ res['Set-Cookie'].should == cookie
230
+ res.body.should include('"foo"=>"bar"')
231
+ end
232
+
233
+ session = pool.pool.get(sess_id)
234
+ session.size.should == r.size+1
235
+ session['counter'].should be_nil
236
+ session['foo'].should == 'bar'
237
+ end
238
+ end
data/spec/spec.opts ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ -fs
@@ -0,0 +1,14 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+ require 'mongo-store'
4
+ require 'spec'
5
+ require 'spec/autorun'
6
+ require 'rack/mock'
7
+ require 'rack/response'
8
+ require 'thread'
9
+
10
+ Spec::Runner.configure do |config|
11
+ config.before do
12
+ Mongo::Connection.new.db('rack').collection('rack-sessions').drop
13
+ end
14
+ end
metadata ADDED
@@ -0,0 +1,125 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mongo-store
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 1
8
+ - 0
9
+ version: 0.1.0
10
+ platform: ruby
11
+ authors:
12
+ - Jonathan Rudenberg
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-02-23 00:00:00 -05:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: mongo
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ segments:
28
+ - 0
29
+ version: "0"
30
+ type: :runtime
31
+ version_requirements: *id001
32
+ - !ruby/object:Gem::Dependency
33
+ name: rack
34
+ prerelease: false
35
+ requirement: &id002 !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ~>
38
+ - !ruby/object:Gem::Version
39
+ segments:
40
+ - 1
41
+ - 1
42
+ - 0
43
+ version: 1.1.0
44
+ type: :runtime
45
+ version_requirements: *id002
46
+ - !ruby/object:Gem::Dependency
47
+ name: rspec
48
+ prerelease: false
49
+ requirement: &id003 !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ segments:
54
+ - 1
55
+ - 2
56
+ - 9
57
+ version: 1.2.9
58
+ type: :development
59
+ version_requirements: *id003
60
+ - !ruby/object:Gem::Dependency
61
+ name: yard
62
+ prerelease: false
63
+ requirement: &id004 !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ segments:
68
+ - 0
69
+ version: "0"
70
+ type: :development
71
+ version_requirements: *id004
72
+ description:
73
+ email: jonathan@titanous.com
74
+ executables: []
75
+
76
+ extensions: []
77
+
78
+ extra_rdoc_files:
79
+ - LICENSE
80
+ - README.rdoc
81
+ files:
82
+ - .document
83
+ - .gitignore
84
+ - LICENSE
85
+ - README.rdoc
86
+ - Rakefile
87
+ - VERSION
88
+ - lib/mongo-store.rb
89
+ - lib/rack/session/mongo.rb
90
+ - spec/mongo-store_spec.rb
91
+ - spec/spec.opts
92
+ - spec/spec_helper.rb
93
+ has_rdoc: true
94
+ homepage: http://github.com/titanous/mongo-store
95
+ licenses: []
96
+
97
+ post_install_message:
98
+ rdoc_options:
99
+ - --charset=UTF-8
100
+ require_paths:
101
+ - lib
102
+ required_ruby_version: !ruby/object:Gem::Requirement
103
+ requirements:
104
+ - - ">="
105
+ - !ruby/object:Gem::Version
106
+ segments:
107
+ - 0
108
+ version: "0"
109
+ required_rubygems_version: !ruby/object:Gem::Requirement
110
+ requirements:
111
+ - - ">="
112
+ - !ruby/object:Gem::Version
113
+ segments:
114
+ - 0
115
+ version: "0"
116
+ requirements: []
117
+
118
+ rubyforge_project:
119
+ rubygems_version: 1.3.6
120
+ signing_key:
121
+ specification_version: 3
122
+ summary: Rack session store for MongoDB
123
+ test_files:
124
+ - spec/mongo-store_spec.rb
125
+ - spec/spec_helper.rb