mongo-store 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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