rack-datamapper 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt ADDED
@@ -0,0 +1,20 @@
1
+ === 0.2.2 / 2009-10-05
2
+
3
+ * various bug fixes in datamapper_store
4
+
5
+ === 0.2.0 / 2009-09-09
6
+
7
+ * added more specs for the session store
8
+
9
+ * added java SoftHashMap to avoid memory leaks of the session cache (when used with jruby and java >=1.5)
10
+
11
+ * some bug fixes with the cache
12
+
13
+ * made it a rubyforge.org project and deployed a gem for it
14
+
15
+ === 0.1.0 / 2009-02-09
16
+
17
+ * 1 major enhancement
18
+
19
+ * Birthday!
20
+
data/Manifest.txt ADDED
@@ -0,0 +1,18 @@
1
+ History.txt
2
+ Manifest.txt
3
+ README.txt
4
+ Rakefile
5
+ lib/rack_datamapper.rb
6
+ lib/softhashmap.jar
7
+ lib/rack_datamapper/identity_maps.rb
8
+ lib/rack_datamapper/restful_transactions.rb
9
+ lib/rack_datamapper/session/abstract/store.rb
10
+ lib/rack_datamapper/session/datamapper.rb
11
+ lib/rack_datamapper/transaction_boundaries.rb
12
+ lib/rack_datamapper/version.rb
13
+ spec/datamapper_session_spec.rb
14
+ spec/identity_maps_spec.rb
15
+ spec/restful_transactions_spec.rb
16
+ spec/spec.opts
17
+ spec/spec_helper.rb
18
+ spec/transaction_boundaries_spec.rb
data/README.txt ADDED
@@ -0,0 +1,65 @@
1
+ = rack_datamapper
2
+
3
+ * http://github.com/mkristian/rack_datamapper
4
+
5
+ * http://rack-datamapper.rubyforge.org
6
+
7
+ == DESCRIPTION:
8
+
9
+ this collection of plugins helps to add datamapper functionality to Rack. there is a IdentityMaps plugin which wrappes the request and with it all database actions are using that identity map. the transaction related plugin TransactionBoundaries and RestfulTransactions wrappes the request into a transaction. for using datamapper to store session data there is the DatamapperStore.
10
+
11
+ === DataMapper::Session::Abstract::Store
12
+
13
+ this is actual store class which can be wrapped to be used in a specific environement, i.e. Rack::Session::Datamapper. this store can the same options as the session store from rack, see
14
+
15
+ * http://rack.rubyforge.org/doc/Rack/Session/Pool.html
16
+
17
+ * http://rack.rubyforge.org/doc/Rack/Session/Abstract/ID.html
18
+
19
+ there are two more options
20
+
21
+ * :session_class - (optional) must be a DataMapper::Resource with session_id, data properties.
22
+
23
+ * :cache - Boolean (default: false) if set to true the store will first try to retrieve the session from a memory cache otherwise fallback to the session_class resource. in case the platform is java (jruby) the cache uses SoftReferences which clears the cache on severe memory shortage, but it needs java 1.5 or higher for this.
24
+
25
+ == Rack Middleware
26
+
27
+ all these middleware take the name of the datamapper repository (which you configure via DataMapper.setup(:name, ....) as second constructor argument (default is :default)
28
+
29
+ === DataMapper::RestfulTransactions
30
+
31
+ wrappers the request inside an transaction for POST,PUT,DELETE methods
32
+
33
+ === DataMapper::TransactionBoundaries
34
+
35
+ wrappers the all request inside an transaction
36
+
37
+ === DataMapper::IdentityMaps
38
+
39
+ wrappers the all request inside an identity scope
40
+
41
+
42
+ == LICENSE:
43
+
44
+ (The MIT License)
45
+
46
+ Copyright (c) 2009 Kristian Meier
47
+
48
+ Permission is hereby granted, free of charge, to any person obtaining
49
+ a copy of this software and associated documentation files (the
50
+ 'Software'), to deal in the Software without restriction, including
51
+ without limitation the rights to use, copy, modify, merge, publish,
52
+ distribute, sublicense, and/or sell copies of the Software, and to
53
+ permit persons to whom the Software is furnished to do so, subject to
54
+ the following conditions:
55
+
56
+ The above copyright notice and this permission notice shall be
57
+ included in all copies or substantial portions of the Software.
58
+
59
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
60
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
61
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
62
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
63
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
64
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
65
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,33 @@
1
+ # -*- ruby -*-
2
+
3
+ require 'rubygems'
4
+ require 'hoe'
5
+ require './lib/rack_datamapper/version.rb'
6
+
7
+ require 'spec'
8
+ require 'spec/rake/spectask'
9
+ require 'pathname'
10
+ require 'yard'
11
+
12
+ Hoe.new('rack-datamapper', Rack::DataMapper::VERSION) do |p|
13
+ p.developer('mkristian', 'm.kristian@web.de')
14
+ p.extra_deps = ['dm-core']
15
+ end
16
+
17
+ desc 'Install the package as a gem.'
18
+ task :install => [:clean, :package] do
19
+ gem = Dir['pkg/*.gem'].first
20
+ sh "gem install --local #{gem} --no-ri --no-rdoc"
21
+ end
22
+
23
+ desc 'Run specifications'
24
+ Spec::Rake::SpecTask.new(:spec) do |t|
25
+ if File.exists?('spec/spec.opts')
26
+ t.spec_opts << '--options' << 'spec/spec.opts'
27
+ end
28
+ t.spec_files = Pathname.glob('./spec/**/*_spec.rb')
29
+ end
30
+
31
+ YARD::Rake::YardocTask.new
32
+
33
+ # vim: syntax=Ruby
@@ -0,0 +1,9 @@
1
+ require 'pathname'
2
+ require 'extlib/pathname'
3
+ require 'dm-core'
4
+
5
+ require Pathname(__FILE__).dirname / 'rack_datamapper' / 'version'
6
+ require Pathname(__FILE__).dirname / 'rack_datamapper' / 'identity_maps'
7
+ require Pathname(__FILE__).dirname / 'rack_datamapper' / 'restful_transactions'
8
+ require Pathname(__FILE__).dirname / 'rack_datamapper' / 'transaction_boundaries'
9
+ require Pathname(__FILE__).dirname / 'rack_datamapper' / 'session' / 'datamapper'
@@ -0,0 +1,16 @@
1
+ module DataMapper
2
+ class IdentityMaps
3
+ def initialize(app, name = :default)
4
+ @app = app
5
+ @name = name.to_sym
6
+ end
7
+
8
+ def call(env)
9
+ status, headers, response = nil, nil, nil
10
+ DataMapper.repository(@name) do
11
+ status, headers, response = @app.call(env)
12
+ end
13
+ [status, headers, response]
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,32 @@
1
+ require 'rack'
2
+ module DataMapper
3
+ class RestfulTransactions
4
+
5
+ class Rollback < StandardError; end
6
+
7
+ def initialize(app, name = :default)
8
+ @app = app
9
+ @name = name.to_sym
10
+ end
11
+
12
+ def call(env)
13
+ request = ::Rack::Request.new(env)
14
+ if ["POST", "PUT", "DELETE"].include? request.request_method
15
+ status, headers, response = nil, nil, nil
16
+ begin
17
+ transaction = DataMapper::Transaction.new(DataMapper.repository(@name))
18
+ transaction.commit do
19
+ status, headers, response = @app.call(env)
20
+ raise Rollback unless [301, 302, 303, 307].include?(status)
21
+ end
22
+ rescue Rollback
23
+ # ignore,
24
+ # this is just needed to trigger the rollback on the transaction
25
+ end
26
+ [status, headers, response]
27
+ else
28
+ @app.call(env)
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,118 @@
1
+ require 'dm-core'
2
+
3
+ module DataMapper
4
+ module Session
5
+ module Abstract
6
+ class Store
7
+
8
+ def initialize(app, options, id_generator)
9
+ @mutex = Mutex.new
10
+ if options.delete(:cache)
11
+ @@cache = if RUBY_PLATFORM =~ /java/
12
+ begin
13
+ # to avoid memory leaks use a hashmap which clears
14
+ # itself on severe memory shortage
15
+ require 'softhashmap'
16
+ m = Java.SoftHashMap.new
17
+ def m.delete(key)
18
+ remove(key)
19
+ end
20
+ m
21
+ rescue
22
+ # fallback to non java Hash
23
+ {}
24
+ end
25
+ else
26
+ {}
27
+ end
28
+ @@semaphore = Mutex.new
29
+ else
30
+ @@cache = nil unless self.class.class_variable_defined? :@@cache
31
+ end
32
+ @@session_class = options.delete(:session_class) || Session unless (self.class.class_variable_defined?(:@@session_class) and @@session_class)
33
+ @id_generator = id_generator
34
+ end
35
+
36
+ def get_session(env, sid)
37
+ @mutex.lock if env['rack.multithread']
38
+ if sid
39
+ session =
40
+ if @@cache
41
+ @@cache[sid] || @@cache[sid] = @@session_class.get(sid)
42
+ else
43
+ @@session_class.get(sid)
44
+ end
45
+ end
46
+
47
+ unless sid and session
48
+ env['rack.errors'].puts("Session '#{sid.inspect}' not found, initializing...") if $VERBOSE and not sid.nil?
49
+ sid = @id_generator.call
50
+ session = @@session_class.create(:session_id => sid)
51
+ @@cache[sid] = session if @@cache
52
+ end
53
+ #session.instance_variable_set('@old', {}.merge(session))
54
+
55
+ return [sid, session.data]
56
+ ensure
57
+ @mutex.unlock if env['rack.multithread']
58
+ end
59
+
60
+ def set_session(env, sid, session_data, options)
61
+ @mutex.lock if env['rack.multithread']
62
+ session =
63
+ if @@cache
64
+ @@cache[sid] || @@cache[sid] = @@session_class.get(sid)
65
+ else
66
+ @@session_class.get(sid)
67
+ end
68
+ return false if session.nil?
69
+ if options[:renew] or options[:drop]
70
+ @@cache.delete(sid) if @@cache
71
+ session.destroy
72
+ return false if options[:drop]
73
+ sid = @id_generator.call
74
+ session = @@session_class.create(:session_id => sid)
75
+ @@cache[sid] = session if @@cache
76
+ end
77
+ # old_session = new_session.instance_variable_get('@old') || {}
78
+ # session = merge_sessions session_id, old_session, new_session, session
79
+ session.data = session_data
80
+ if session_data.empty?
81
+ @@cache.delete(sid) if @@cache
82
+ session.destroy
83
+ false
84
+ elsif session.save
85
+ session.session_id
86
+ else
87
+ false
88
+ end
89
+ ensure
90
+ @mutex.unlock if env['rack.multithread']
91
+ end
92
+ end
93
+
94
+ class Session
95
+
96
+ include ::DataMapper::Resource
97
+
98
+ def self.default_storage_name
99
+ "Session"
100
+ end
101
+
102
+ property :session_id, String, :key => true
103
+
104
+ property :data, Text, :nullable => false, :default => ::Base64.encode64(Marshal.dump({}))
105
+
106
+ property :updated_at, DateTime, :nullable => true, :index => true
107
+
108
+ def data=(data)
109
+ attribute_set(:data, ::Base64.encode64(Marshal.dump(data)))
110
+ end
111
+
112
+ def data
113
+ Marshal.load(::Base64.decode64(attribute_get(:data)))
114
+ end
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,27 @@
1
+ require 'rack/session/abstract/id'
2
+ require 'rack_datamapper/session/abstract/store'
3
+
4
+ module DataMapper
5
+ module Session
6
+ class Datamapper < ::Rack::Session::Abstract::ID
7
+
8
+ def initialize(app, options = {})
9
+ super
10
+ id_generator = Proc.new do
11
+ generate_sid
12
+ end
13
+ @store = ::DataMapper::Session::Abstract::Store.new(app, options, id_generator)
14
+ end
15
+
16
+ private
17
+
18
+ def get_session(env, sid)
19
+ @store.get_session(env, sid)
20
+ end
21
+
22
+ def set_session(env, sid, session_data, options)
23
+ @store.set_session(env, sid, session_data, options)
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,25 @@
1
+ module DataMapper
2
+ class TransactionBoundaries
3
+
4
+ class Rollback < StandardError; end
5
+
6
+ def initialize(app, name = :default)
7
+ @app = app
8
+ @name = name.to_sym
9
+ end
10
+
11
+ def call(env)
12
+ status, headers, response = nil, nil, nil
13
+ begin
14
+ transaction = DataMapper::Transaction.new(DataMapper.repository(@name))
15
+ transaction.commit do
16
+ status, headers, response = @app.call(env)
17
+ raise Rollback if status >= 400 or status < 200
18
+ end
19
+ rescue Rollback
20
+ # ignore, needed to trigger the rollback on the transaction
21
+ end
22
+ [status, headers, response]
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,5 @@
1
+ module Rack
2
+ module DataMapper
3
+ VERSION = '0.2.2'.freeze
4
+ end
5
+ end
Binary file
@@ -0,0 +1,186 @@
1
+ require 'pathname'
2
+ require Pathname(__FILE__).dirname.expand_path + 'spec_helper'
3
+
4
+ require 'rack/mock'
5
+ require 'rack/response'
6
+ require 'thread'
7
+
8
+ [{}, { :cache => true }].each do |options|
9
+ describe "DataMapper::Session::Datamapper with options = #{options.inspect}" do
10
+
11
+ before :each do
12
+ @session_key = DataMapper::Session::Datamapper::DEFAULT_OPTIONS[:key] || "rack.session"
13
+ @session_match = /#{@session_key}=[0-9a-fA-F]+;/
14
+ @incrementor = lambda do |env|
15
+ env["rack.session"]["counter"] ||= 0
16
+ env["rack.session"]["counter"] += 1
17
+ Rack::Response.new(env["rack.session"].inspect).to_a
18
+ end
19
+
20
+ @drop_session = proc do |env|
21
+ env['rack.session.options'][:drop] = true
22
+ @incrementor.call(env)
23
+ end
24
+
25
+ @renew_session = proc do |env|
26
+ env['rack.session.options'][:renew] = true
27
+ @incrementor.call(env)
28
+ end
29
+
30
+ @defer_session = proc do |env|
31
+ env['rack.session.options'][:defer] = true
32
+ @incrementor.call(env)
33
+ end
34
+
35
+ @session_class = DataMapper::Session::Abstract::Session
36
+ @session_class.auto_migrate!
37
+ end
38
+
39
+ it "should creates a new cookie" do
40
+ pool = DataMapper::Session::Datamapper.new(@incrementor, options)
41
+ res = Rack::MockRequest.new(pool).get("/")
42
+ res["Set-Cookie"].should =~ @session_match
43
+ res.body.should == '{"counter"=>1}'
44
+ end
45
+
46
+ it "should determines session from a cookie" do
47
+ pool = DataMapper::Session::Datamapper.new(@incrementor, options)
48
+ req = Rack::MockRequest.new(pool)
49
+ cookie = req.get("/")["Set-Cookie"]
50
+ req.get("/", "HTTP_COOKIE" => cookie).
51
+ body.should == '{"counter"=>2}'
52
+ req.get("/", "HTTP_COOKIE" => cookie).
53
+ body.should == '{"counter"=>3}'
54
+ end
55
+
56
+ it "survives nonexistant cookies" do
57
+ pool = DataMapper::Session::Datamapper.new(@incrementor, options)
58
+ res = Rack::MockRequest.new(pool).
59
+ get("/", "HTTP_COOKIE" => "#{@session_key}=blarghfasel")
60
+ res.body.should == '{"counter"=>1}'
61
+ end
62
+
63
+ it "should delete cookies with :drop option" do
64
+ pending if Rack.release < "1.0"
65
+ pool = DataMapper::Session::Datamapper.new(@incrementor, options)
66
+ req = Rack::MockRequest.new(pool)
67
+ drop = Rack::Utils::Context.new(pool, @drop_session)
68
+ dreq = Rack::MockRequest.new(drop)
69
+
70
+ res0 = req.get("/")
71
+ session = (cookie = res0["Set-Cookie"])[@session_match]
72
+ res0.body.should == '{"counter"=>1}'
73
+ @session_class.all.size.should == 1
74
+
75
+ res1 = req.get("/", "HTTP_COOKIE" => cookie)
76
+ res1["Set-Cookie"][@session_match].should == session
77
+ res1.body.should == '{"counter"=>2}'
78
+ @session_class.all.size.should == 1
79
+
80
+ res2 = dreq.get("/", "HTTP_COOKIE" => cookie)
81
+ res2["Set-Cookie"].should be_nil
82
+ res2.body.should == '{"counter"=>3}'
83
+ @session_class.all.size.should == 0
84
+
85
+ res3 = req.get("/", "HTTP_COOKIE" => cookie)
86
+ res3["Set-Cookie"][@session_match].should_not == session
87
+ res3.body.should == '{"counter"=>1}'
88
+ @session_class.all.size.should == 1
89
+ end
90
+
91
+ it "provides new session id with :renew option" do
92
+ pending if Rack.release < "1.0"
93
+ pool = DataMapper::Session::Datamapper.new(@incrementor, options)
94
+ req = Rack::MockRequest.new(pool)
95
+ renew = Rack::Utils::Context.new(pool, @renew_session)
96
+ rreq = Rack::MockRequest.new(renew)
97
+
98
+ res0 = req.get("/")
99
+ session = (cookie = res0["Set-Cookie"])[@session_match]
100
+ res0.body.should == '{"counter"=>1}'
101
+ @session_class.all.size.should == 1
102
+
103
+ res1 = req.get("/", "HTTP_COOKIE" => cookie)
104
+ res1["Set-Cookie"][@session_match].should == session
105
+ res1.body.should == '{"counter"=>2}'
106
+ @session_class.all.size.should == 1
107
+
108
+ res2 = rreq.get("/", "HTTP_COOKIE" => cookie)
109
+ new_cookie = res2["Set-Cookie"]
110
+ new_session = new_cookie[@session_match]
111
+ new_session.should_not == session
112
+ res2.body.should == '{"counter"=>3}'
113
+ @session_class.all.size.should == 1
114
+
115
+ res3 = req.get("/", "HTTP_COOKIE" => new_cookie)
116
+ res3["Set-Cookie"][@session_match].should == new_session
117
+ res3.body.should == '{"counter"=>4}'
118
+ @session_class.all.size.should == 1
119
+ end
120
+
121
+ it "omits cookie with :defer option" do
122
+ pending if Rack.release < "1.0"
123
+ pool = DataMapper::Session::Datamapper.new(@incrementor, options)
124
+ req = Rack::MockRequest.new(pool)
125
+ defer = Rack::Utils::Context.new(pool, @defer_session)
126
+ dreq = Rack::MockRequest.new(defer)
127
+
128
+ res0 = req.get("/")
129
+ session = (cookie = res0["Set-Cookie"])[@session_match]
130
+ res0.body.should == '{"counter"=>1}'
131
+ @session_class.all.size.should == 1
132
+
133
+ res1 = req.get("/", "HTTP_COOKIE" => cookie)
134
+ res1["Set-Cookie"][@session_match].should == session
135
+ res1.body.should == '{"counter"=>2}'
136
+ @session_class.all.size.should == 1
137
+
138
+ res2 = dreq.get("/", "HTTP_COOKIE" => cookie)
139
+ res2["Set-Cookie"].should be_nil
140
+ res2.body.should == '{"counter"=>3}'
141
+ @session_class.all.size.should == 1
142
+
143
+ res3 = req.get("/", "HTTP_COOKIE" => cookie)
144
+ res3["Set-Cookie"][@session_match].should == session
145
+ res3.body.should == '{"counter"=>4}'
146
+ @session_class.all.size.should == 1
147
+ end
148
+
149
+ # anyone know how to do this better?
150
+ it "should merge sessions with multithreading on" do
151
+ next unless $DEBUG
152
+ warn 'Running multithread tests for Session::Pool'
153
+ pool = DataMapper::Session::Datamapper.new(@incrementor, options)
154
+ req = Rack::MockRequest.new(pool)
155
+
156
+ res = req.get('/')
157
+ res.body.should == '{"counter"=>1}'
158
+ cookie = res["Set-Cookie"]
159
+ sess_id = cookie[/#{pool.key}=([^,;]+)/,1]
160
+
161
+ delta_incrementor = lambda do |env|
162
+ # emulate disconjoinment of threading
163
+ env['rack.session'] = env['rack.session'].dup
164
+ Thread.stop
165
+ env['rack.session'][(Time.now.usec*rand).to_i] = true
166
+ @incrementor.call(env)
167
+ end
168
+ tses = Rack::Utils::Context.new pool, delta_incrementor
169
+ treq = Rack::MockRequest.new(tses)
170
+ tnum = rand(7).to_i+5
171
+ r = Array.new(tnum) do
172
+ Thread.new(treq) do |run|
173
+ run.get('/', "HTTP_COOKIE" => cookie, 'rack.multithread' => true)
174
+ end
175
+ end.reverse.map{|t| t.run.join.value }
176
+ r.each do |res|
177
+ res['Set-Cookie'].should == cookie
178
+ res.body.include('"counter"=>2').should be_true
179
+ end
180
+
181
+ session = @session_class.get(sess_id)
182
+ session.size.should == tnum+1 # counter
183
+ session['counter'].should == 2 # meeeh
184
+ end
185
+ end
186
+ end
@@ -0,0 +1,46 @@
1
+ require 'pathname'
2
+ require Pathname(__FILE__).dirname.expand_path + 'spec_helper'
3
+
4
+ describe DataMapper::IdentityMaps do
5
+
6
+ class Name
7
+ include DataMapper::Resource
8
+
9
+ property :id, Serial
10
+ property :name, String
11
+ end
12
+
13
+ DataMapper.auto_migrate!
14
+
15
+ class App
16
+ def initialize(status = 200, headers = "", response = "", &block)
17
+ @status, @headers, @response, @block = status, headers, response, block
18
+ end
19
+
20
+ def call(env)
21
+ @block.call
22
+ [@status, @headers, @response]
23
+ end
24
+ end
25
+
26
+ after :each do
27
+ Name.all.destroy!
28
+ end
29
+
30
+ it 'should collect resources loaded from the datasource' do
31
+ app = App.new do
32
+ Name.create(:name => 'first')
33
+ repository.identity_map(Name).size.should == 1
34
+ Name.create(:name => 'second')
35
+ repository.identity_map(Name).size.should == 2
36
+ Name.create(:name => 'third')
37
+ repository.identity_map(Name).size.should == 3
38
+ end
39
+ DataMapper::IdentityMaps.new(app).call(nil)
40
+
41
+ Name.all.size.should == 3
42
+
43
+ repository.identity_map(Name).size.should == 0
44
+ end
45
+
46
+ end
@@ -0,0 +1,112 @@
1
+ require 'pathname'
2
+ require Pathname(__FILE__).dirname.expand_path + 'spec_helper'
3
+
4
+
5
+ describe DataMapper::RestfulTransactions do
6
+
7
+ class Name
8
+ include DataMapper::Resource
9
+
10
+ property :id, Serial
11
+ property :name, String
12
+ end
13
+
14
+ DataMapper.auto_migrate!
15
+
16
+ class RestfulTransactionsApp
17
+ def initialize(status, headers = "", response = "", &block)
18
+ @status, @headers, @response, @block = status, headers, response, block
19
+ end
20
+
21
+ def call(env)
22
+ @block.call
23
+ [@status, @headers, @response]
24
+ end
25
+ end
26
+ def mock_request(stubs={})
27
+ @mock_request ||= mock(::Rack::Request, stubs)
28
+ end
29
+
30
+ before :each do
31
+ ::Rack::Request.stub!(:new).with(nil).and_return(mock_request)
32
+ end
33
+
34
+ after :each do
35
+ Name.all.destroy!
36
+ end
37
+
38
+ it 'should commit on redirects unless it is GET request' do
39
+ mock_request.should_receive(:request_method).any_number_of_times.and_return("POST")
40
+ app = RestfulTransactionsApp.new(301) do
41
+ Name.create(:name => 'first')
42
+ end
43
+ DataMapper::RestfulTransactions.new(app).call(nil)
44
+ Name.all.size.should == 1
45
+ Name.first.name.should == 'first'
46
+
47
+ app = App.new(302) do
48
+ Name.create(:name => 'second')
49
+ end
50
+ DataMapper::RestfulTransactions.new(app).call(nil)
51
+ Name.all.size.should == 2
52
+ Name.all.last.name.should == 'second'
53
+
54
+ app = RestfulTransactionsApp.new(303) do
55
+ Name.create(:name => 'third')
56
+ end
57
+ DataMapper::RestfulTransactions.new(app).call(nil)
58
+ Name.all.size.should == 3
59
+ Name.all.last.name.should == 'third'
60
+
61
+ app = RestfulTransactionsApp.new(307) do
62
+ Name.create(:name => 'forth')
63
+ end
64
+ DataMapper::RestfulTransactions.new(app).call(nil)
65
+ Name.all.size.should == 4
66
+ Name.all.last.name.should == 'forth'
67
+ end
68
+
69
+ it 'should have no transaction on GET requests' do
70
+ mock_request.should_receive(:request_method).any_number_of_times.and_return("GET")
71
+ app = RestfulTransactionsApp.new(200) do
72
+ Name.create(:name => 'first')
73
+ end
74
+ DataMapper::RestfulTransactions.new(app).call(nil)
75
+ Name.all.size.should == 1
76
+
77
+ app = RestfulTransactionsApp.new(500) do
78
+ Name.create(:name => 'second')
79
+ raise "error"
80
+ end
81
+ lambda { DataMapper::RestfulTransactions.new(app).call(nil) }.should raise_error
82
+ Name.all.size.should == 2
83
+ end
84
+
85
+ it 'should rollback when status is not redirect and method is not GET' do
86
+ mock_request.should_receive(:request_method).any_number_of_times.and_return("PUT")
87
+ app = RestfulTransactionsApp.new(200) do
88
+ Name.create(:name => 'first')
89
+ # TODO is this read uncommited ?
90
+ Name.all.size.should == 1
91
+ end
92
+ DataMapper::RestfulTransactions.new(app).call(nil)
93
+ Name.all.size.should == 0
94
+
95
+ app = RestfulTransactionsApp.new(404) do
96
+ Name.create(:name => 'first')
97
+ # TODO is this read uncommited ?
98
+ Name.all.size.should == 1
99
+ end
100
+ DataMapper::RestfulTransactions.new(app).call(nil)
101
+ Name.all.size.should == 0
102
+
103
+ app = RestfulTransactionsApp.new(503) do
104
+ Name.create(:name => 'first')
105
+ # TODO is this read uncommited ?
106
+ Name.all.size.should == 1
107
+ end
108
+ DataMapper::RestfulTransactions.new(app).call(nil)
109
+ Name.all.size.should == 0
110
+ end
111
+
112
+ end
data/spec/spec.opts ADDED
@@ -0,0 +1 @@
1
+ --colour
@@ -0,0 +1,27 @@
1
+ require 'pathname'
2
+ require 'rubygems'
3
+
4
+ gem 'rspec', '~>1.2'
5
+ require 'spec'
6
+
7
+ $LOAD_PATH << Pathname(__FILE__).dirname.parent.expand_path + 'lib'
8
+ require 'rack_datamapper'
9
+
10
+ def load_driver(name, default_uri)
11
+ return false if ENV['ADAPTER'] != name.to_s
12
+
13
+ begin
14
+ DataMapper.setup(name, ENV["#{name.to_s.upcase}_SPEC_URI"] || default_uri)
15
+ DataMapper::Repository.adapters[:default] = DataMapper::Repository.adapters[name]
16
+ true
17
+ rescue LoadError => e
18
+ warn "Could not load do_#{name}: #{e}"
19
+ false
20
+ end
21
+ end
22
+
23
+ ENV['ADAPTER'] ||= 'sqlite3'
24
+
25
+ HAS_SQLITE3 = load_driver(:sqlite3, 'sqlite3::memory:')
26
+ HAS_MYSQL = load_driver(:mysql, 'mysql://localhost/dm_core_test')
27
+ HAS_POSTGRES = load_driver(:postgres, 'postgres://postgres@localhost/dm_core_test')
@@ -0,0 +1,86 @@
1
+ require 'pathname'
2
+ require Pathname(__FILE__).dirname.expand_path + 'spec_helper'
3
+
4
+ describe DataMapper::TransactionBoundaries do
5
+
6
+ class Name
7
+ include DataMapper::Resource
8
+
9
+ property :id, Serial
10
+ property :name, String
11
+ end
12
+
13
+ DataMapper.auto_migrate!
14
+
15
+ class TransactionBoundariesApp
16
+ def initialize(status, headers = "", response = "", &block)
17
+ @status, @headers, @response, @block = status, headers, response, block
18
+ end
19
+
20
+ def call(env)
21
+ @block.call
22
+ [@status, @headers, @response]
23
+ end
24
+ end
25
+
26
+ after :each do
27
+ Name.all.destroy!
28
+ end
29
+
30
+ it 'should commit on status < 400 and status >= 200' do
31
+ app = TransactionBoundariesApp.new(301) do
32
+ Name.create(:name => 'first')
33
+ end
34
+ DataMapper::TransactionBoundaries.new(app).call(nil)
35
+ Name.all.size.should == 1
36
+ Name.first.name.should == 'first'
37
+
38
+ app = TransactionBoundariesApp.new(200) do
39
+ Name.create(:name => 'second')
40
+ end
41
+ DataMapper::TransactionBoundaries.new(app).call(nil)
42
+ Name.all.size.should == 2
43
+ Name.all.last.name.should == 'second'
44
+
45
+ app = TransactionBoundariesApp.new(303) do
46
+ Name.create(:name => 'third')
47
+ end
48
+ DataMapper::TransactionBoundaries.new(app).call(nil)
49
+ Name.all.size.should == 3
50
+ Name.all.last.name.should == 'third'
51
+
52
+ app = TransactionBoundariesApp.new(222) do
53
+ Name.create(:name => 'forth')
54
+ end
55
+ DataMapper::TransactionBoundaries.new(app).call(nil)
56
+ Name.all.size.should == 4
57
+ Name.all.last.name.should == 'forth'
58
+ end
59
+
60
+ it 'should rollback on status < 200 or status >= 400' do
61
+ app = TransactionBoundariesApp.new(100) do
62
+ Name.create(:name => 'first')
63
+ # TODO is this read uncommited ?
64
+ Name.all.size.should == 1
65
+ end
66
+ DataMapper::TransactionBoundaries.new(app).call(nil)
67
+ Name.all.size.should == 0
68
+
69
+ app = TransactionBoundariesApp.new(404) do
70
+ Name.create(:name => 'first')
71
+ # TODO is this read uncommited ?
72
+ Name.all.size.should == 1
73
+ end
74
+ DataMapper::TransactionBoundaries.new(app).call(nil)
75
+ Name.all.size.should == 0
76
+
77
+ app = TransactionBoundariesApp.new(500) do
78
+ Name.create(:name => 'first')
79
+ # TODO is this read uncommited ?
80
+ Name.all.size.should == 1
81
+ end
82
+ DataMapper::TransactionBoundaries.new(app).call(nil)
83
+ Name.all.size.should == 0
84
+ end
85
+
86
+ end
metadata ADDED
@@ -0,0 +1,95 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rack-datamapper
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.2
5
+ platform: ruby
6
+ authors:
7
+ - mkristian
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-10-05 00:00:00 +05:30
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: dm-core
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: hoe
27
+ type: :development
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 1.8.3
34
+ version:
35
+ description: this collection of plugins helps to add datamapper functionality to Rack. there is a IdentityMaps plugin which wrappes the request and with it all database actions are using that identity map. the transaction related plugin TransactionBoundaries and RestfulTransactions wrappes the request into a transaction. for using datamapper to store session data there is the DatamapperStore.
36
+ email:
37
+ - m.kristian@web.de
38
+ executables: []
39
+
40
+ extensions: []
41
+
42
+ extra_rdoc_files:
43
+ - History.txt
44
+ - Manifest.txt
45
+ - README.txt
46
+ files:
47
+ - History.txt
48
+ - Manifest.txt
49
+ - README.txt
50
+ - Rakefile
51
+ - lib/rack_datamapper.rb
52
+ - lib/softhashmap.jar
53
+ - lib/rack_datamapper/identity_maps.rb
54
+ - lib/rack_datamapper/restful_transactions.rb
55
+ - lib/rack_datamapper/session/abstract/store.rb
56
+ - lib/rack_datamapper/session/datamapper.rb
57
+ - lib/rack_datamapper/transaction_boundaries.rb
58
+ - lib/rack_datamapper/version.rb
59
+ - spec/datamapper_session_spec.rb
60
+ - spec/identity_maps_spec.rb
61
+ - spec/restful_transactions_spec.rb
62
+ - spec/spec.opts
63
+ - spec/spec_helper.rb
64
+ - spec/transaction_boundaries_spec.rb
65
+ has_rdoc: true
66
+ homepage: http://github.com/mkristian/rack_datamapper
67
+ licenses: []
68
+
69
+ post_install_message:
70
+ rdoc_options:
71
+ - --main
72
+ - README.txt
73
+ require_paths:
74
+ - lib
75
+ required_ruby_version: !ruby/object:Gem::Requirement
76
+ requirements:
77
+ - - ">="
78
+ - !ruby/object:Gem::Version
79
+ version: "0"
80
+ version:
81
+ required_rubygems_version: !ruby/object:Gem::Requirement
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ version: "0"
86
+ version:
87
+ requirements: []
88
+
89
+ rubyforge_project: rack-datamapper
90
+ rubygems_version: 1.3.4
91
+ signing_key:
92
+ specification_version: 3
93
+ summary: this collection of plugins helps to add datamapper functionality to Rack
94
+ test_files: []
95
+