Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

initial import after porting code from datamapper4rails to rack

  • Loading branch information...
commit e0cee5c4d0e6ecf35c406156c7e7c74de7572b35 1 parent ef9bd7f
@mkristian authored
View
2  .gitignore
@@ -0,0 +1,2 @@
+/pkg
+*~
View
6 History.txt
@@ -0,0 +1,6 @@
+=== 1.0.0 / 2009-02-09
+
+* 1 major enhancement
+
+ * Birthday!
+
View
17 Manifest.txt
@@ -0,0 +1,17 @@
+History.txt
+Manifest.txt
+README.txt
+Rakefile
+lib/rack_datamapper.rb
+lib/rack_datamapper/identity_maps.rb
+lib/rack_datamapper/restful_transactions.rb
+lib/rack_datamapper/session/abstract/store.rb
+lib/rack_datamapper/session/datamapper.rb
+lib/rack_datamapper/transaction_boundaries.rb
+lib/rack_datamapper/version.rb
+spec/datamapper_session_spec.rb
+spec/identity_maps_spec.rb
+spec/restful_transactions_spec.rb
+spec/spec.opts
+spec/spec_helper.rb
+spec/transaction_boundaries_spec.rb
View
0  README
No changes.
View
32 README.txt
@@ -0,0 +1,32 @@
+= rack_datamapper
+
+* http://github.com/mkristian/rack_datamapper
+
+== 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.
+
+== LICENSE:
+
+(The MIT License)
+
+Copyright (c) 2009 Kristian Meier
+
+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:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+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.
View
35 Rakefile
@@ -0,0 +1,35 @@
+# -*- ruby -*-
+
+require 'rubygems'
+require 'hoe'
+require './lib/rack_datamapper/version.rb'
+
+require 'spec'
+require 'spec/rake/spectask'
+require 'pathname'
+require 'yard'
+
+Hoe.new('rack_datamapper', Rack::DataMapper::VERSION) do |p|
+ # p.rubyforge_name = 'dm-utf8x' # if different than lowercase project name
+ p.developer('mkristian', 'm.kristian@web.de')
+end
+
+desc 'Install the package as a gem.'
+task :install => [:clean, :package] do
+ gem = Dir['pkg/*.gem'].first
+ sh "gem install --local #{gem} --no-ri --no-rdoc"
+end
+
+desc 'Run specifications'
+Spec::Rake::SpecTask.new(:spec) do |t|
+ if File.exists?('spec/spec.opts')
+ t.spec_opts << '--options' << 'spec/spec.opts'
+ end
+ t.spec_files = Pathname.glob('./spec/**/*_spec.rb')
+end
+
+require 'yard'
+
+YARD::Rake::YardocTask.new
+
+# vim: syntax=Ruby
View
9 lib/rack_datamapper.rb
@@ -0,0 +1,9 @@
+require 'pathname'
+require 'extlib/pathname'
+require 'dm-core'
+
+require Pathname(__FILE__).dirname / 'rack_datamapper' / 'version'
+require Pathname(__FILE__).dirname / 'rack_datamapper' / 'identity_maps'
+require Pathname(__FILE__).dirname / 'rack_datamapper' / 'restful_transactions'
+require Pathname(__FILE__).dirname / 'rack_datamapper' / 'transaction_boundaries'
+require Pathname(__FILE__).dirname / 'rack_datamapper' / 'session' / 'datamapper'
View
16 lib/rack_datamapper/identity_maps.rb
@@ -0,0 +1,16 @@
+module DataMapper
+ class IdentityMaps
+ def initialize(app, name = :default)
+ @app = app
+ @name = name.to_sym
+ end
+
+ def call(env)
+ status, headers, response = nil, nil, nil
+ DataMapper.repository(@name) do
+ status, headers, response = @app.call(env)
+ end
+ [status, headers, response]
+ end
+ end
+end
View
32 lib/rack_datamapper/restful_transactions.rb
@@ -0,0 +1,32 @@
+require 'rack'
+module DataMapper
+ class RestfulTransactions
+
+ class Rollback < StandardError; end
+
+ def initialize(app, name = :default)
+ @app = app
+ @name = name.to_sym
+ end
+
+ def call(env)
+ request = ::Rack::Request.new(env)
+ if ["POST", "PUT", "DELETE"].include? request.request_method
+ status, headers, response = nil, nil, nil
+ begin
+ transaction = DataMapper::Transaction.new(DataMapper.repository(@name))
+ transaction.commit do
+ status, headers, response = @app.call(env)
+ raise Rollback unless [301, 302, 303, 307].include?(status)
+ end
+ rescue Rollback
+ # ignore,
+ # this is just needed to trigger the rollback on the transaction
+ end
+ [status, headers, response]
+ else
+ @app.call(env)
+ end
+ end
+ end
+end
View
97 lib/rack_datamapper/session/abstract/store.rb
@@ -0,0 +1,97 @@
+require 'dm-core'
+
+module DataMapper
+ module Session
+ module Abstract
+ class Store
+
+ def initialize(app, options, id_generator)
+ @mutex = Mutex.new
+ if options.delete(:cache)
+ @@cache = {}
+ @@semaphore = Mutex.new
+ else
+ @@cache = nil unless self.class.class_variable_defined? :@@cache
+ end
+ @@session_class = options.delete(:session_class) || Session unless (self.class.class_variable_defined?(:@@session_class) and @@session_class)
+ @id_generator = id_generator
+ end
+
+ def get_session(env, sid)
+ @mutex.lock if env['rack.multithread']
+ if sid
+ session =
+ if @@cache
+ @@cache[sid] || @@session_class.get(sid)
+ else
+ @@session_class.get(sid)
+ end
+ end
+
+ unless sid and session
+ env['rack.errors'].puts("Session '#{sid.inspect}' not found, initializing...") if $VERBOSE and not sid.nil?
+ sid = @id_generator.call
+ session = @@session_class.create(:session_id => sid, :updated_at => Time.now)
+ end
+ #session.instance_variable_set('@old', {}.merge(session))
+
+ return [sid, session.data]
+ ensure
+ @mutex.unlock if env['rack.multithread']
+ end
+
+ def set_session(env, sid, session_data, options)
+ @mutex.lock if env['rack.multithread']
+ session =
+ if @@cache
+ @@cache[sid] || @@session_class.get(sid)
+ else
+ @@session_class.get(sid)
+ end
+return false if session.nil?
+ if options[:renew] or options[:drop]
+ @@cache.delete(sid) if @@cache
+ session.destroy
+ return false if options[:drop]
+ sid = @id_generator.call
+ session = @@session_class.create(:session_id => sid, :updated_at => Time.now)
+ @@cache[sid] = session if @@cache
+ end
+# old_session = new_session.instance_variable_get('@old') || {}
+# session = merge_sessions session_id, old_session, new_session, session
+ session.data = session_data
+ if session.save
+ session.session_id
+ else
+ false
+ end
+ ensure
+ @mutex.unlock if env['rack.multithread']
+ end
+ end
+
+ class Session
+
+ include ::DataMapper::Resource
+
+ def self.name
+ "session"
+ end
+
+ property :session_id, String, :key => true
+
+ property :data, Text, :nullable => false, :default => ::Base64.encode64(Marshal.dump({}))
+
+ property :updated_at, DateTime, :nullable => true, :index => true
+
+ def data=(data)
+ attribute_set(:data, ::Base64.encode64(Marshal.dump(data)))
+ end
+
+ def data
+ Marshal.load(::Base64.decode64(attribute_get(:data)))
+ end
+ end
+ end
+ end
+end
View
27 lib/rack_datamapper/session/datamapper.rb
@@ -0,0 +1,27 @@
+require 'rack/session/abstract/id'
+require 'rack_datamapper/session/abstract/store'
+
+module DataMapper
+ module Session
+ class Datamapper < ::Rack::Session::Abstract::ID
+
+ def initialize(app, options = {})
+ super
+ id_generator = Proc.new do
+ generate_sid
+ end
+ @store = ::DataMapper::Session::Abstract::Store.new(app, options, id_generator)
+ end
+
+ private
+
+ def get_session(env, sid)
+ @store.get_session(env, sid)
+ end
+
+ def set_session(env, sid, session_data, options)
+ @store.set_session(env, sid, session_data, options)
+ end
+ end
+ end
+end
View
25 lib/rack_datamapper/transaction_boundaries.rb
@@ -0,0 +1,25 @@
+module DataMapper
+ class TransactionBoundaries
+
+ class Rollback < StandardError; end
+
+ def initialize(app, name = :default)
+ @app = app
+ @name = name.to_sym
+ end
+
+ def call(env)
+ status, headers, response = nil, nil, nil
+ begin
+ transaction = DataMapper::Transaction.new(DataMapper.repository(@name))
+ transaction.commit do
+ status, headers, response = @app.call(env)
+ raise Rollback if status >= 400 or status < 200
+ end
+ rescue Rollback
+ # ignore, needed to trigger the rollback on the transaction
+ end
+ [status, headers, response]
+ end
+ end
+end
View
186 spec/datamapper_session_spec.rb
@@ -0,0 +1,186 @@
+require 'pathname'
+require Pathname(__FILE__).dirname.expand_path + 'spec_helper'
+
+require 'rack/mock'
+require 'rack/response'
+require 'thread'
+
+[{}, { :cache => true }].each do |options|
+ describe "DataMapper::Session::Datamapper with options = #{options.inspect}" do
+
+ before :each do
+ @session_key = DataMapper::Session::Datamapper::DEFAULT_OPTIONS[:key] || "rack.session"
+ @session_match = /#{@session_key}=[0-9a-fA-F]+;/
+ @incrementor = lambda do |env|
+ env["rack.session"]["counter"] ||= 0
+ env["rack.session"]["counter"] += 1
+ Rack::Response.new(env["rack.session"].inspect).to_a
+ end
+
+ @drop_session = proc do |env|
+ env['rack.session.options'][:drop] = true
+ @incrementor.call(env)
+ end
+
+ @renew_session = proc do |env|
+ env['rack.session.options'][:renew] = true
+ @incrementor.call(env)
+ end
+
+ @defer_session = proc do |env|
+ env['rack.session.options'][:defer] = true
+ @incrementor.call(env)
+ end
+
+ @session_class = DataMapper::Session::Abstract::Session
+ @session_class.auto_migrate!
+ end
+
+ it "should creates a new cookie" do
+ pool = DataMapper::Session::Datamapper.new(@incrementor, options)
+ res = Rack::MockRequest.new(pool).get("/")
+ res["Set-Cookie"].should =~ @session_match
+ res.body.should == '{"counter"=>1}'
+ end
+
+ it "should determines session from a cookie" do
+ pool = DataMapper::Session::Datamapper.new(@incrementor, options)
+ req = Rack::MockRequest.new(pool)
+ cookie = req.get("/")["Set-Cookie"]
+ req.get("/", "HTTP_COOKIE" => cookie).
+ body.should == '{"counter"=>2}'
+ req.get("/", "HTTP_COOKIE" => cookie).
+ body.should == '{"counter"=>3}'
+ end
+
+ it "survives nonexistant cookies" do
+ pool = DataMapper::Session::Datamapper.new(@incrementor, options)
+ res = Rack::MockRequest.new(pool).
+ get("/", "HTTP_COOKIE" => "#{@session_key}=blarghfasel")
+ res.body.should == '{"counter"=>1}'
+ end
+
+ it "should delete cookies with :drop option" do
+ pending if Rack.release < "1.0"
+ pool = DataMapper::Session::Datamapper.new(@incrementor, options)
+ req = Rack::MockRequest.new(pool)
+ drop = Rack::Utils::Context.new(pool, @drop_session)
+ dreq = Rack::MockRequest.new(drop)
+
+ res0 = req.get("/")
+ session = (cookie = res0["Set-Cookie"])[@session_match]
+ res0.body.should == '{"counter"=>1}'
+ @session_class.all.size.should == 1
+
+ res1 = req.get("/", "HTTP_COOKIE" => cookie)
+ res1["Set-Cookie"][@session_match].should == session
+ res1.body.should == '{"counter"=>2}'
+ @session_class.all.size.should == 1
+
+ res2 = dreq.get("/", "HTTP_COOKIE" => cookie)
+ res2["Set-Cookie"].should be_nil
+ res2.body.should == '{"counter"=>3}'
+ @session_class.all.size.should == 0
+
+ res3 = req.get("/", "HTTP_COOKIE" => cookie)
+ res3["Set-Cookie"][@session_match].should_not == session
+ res3.body.should == '{"counter"=>1}'
+ @session_class.all.size.should == 1
+ end
+
+ it "provides new session id with :renew option" do
+ pending if Rack.release < "1.0"
+ pool = DataMapper::Session::Datamapper.new(@incrementor, options)
+ req = Rack::MockRequest.new(pool)
+ renew = Rack::Utils::Context.new(pool, @renew_session)
+ rreq = Rack::MockRequest.new(renew)
+
+ res0 = req.get("/")
+ session = (cookie = res0["Set-Cookie"])[@session_match]
+ res0.body.should == '{"counter"=>1}'
+ @session_class.all.size.should == 1
+
+ res1 = req.get("/", "HTTP_COOKIE" => cookie)
+ res1["Set-Cookie"][@session_match].should == session
+ res1.body.should == '{"counter"=>2}'
+ @session_class.all.size.should == 1
+
+ res2 = rreq.get("/", "HTTP_COOKIE" => cookie)
+ new_cookie = res2["Set-Cookie"]
+ new_session = new_cookie[@session_match]
+ new_session.should_not == session
+ res2.body.should == '{"counter"=>3}'
+ @session_class.all.size.should == 1
+
+ res3 = req.get("/", "HTTP_COOKIE" => new_cookie)
+ res3["Set-Cookie"][@session_match].should == new_session
+ res3.body.should == '{"counter"=>4}'
+ @session_class.all.size.should == 1
+ end
+
+ it "omits cookie with :defer option" do
+ pending if Rack.release < "1.0"
+ pool = DataMapper::Session::Datamapper.new(@incrementor, options)
+ req = Rack::MockRequest.new(pool)
+ defer = Rack::Utils::Context.new(pool, @defer_session)
+ dreq = Rack::MockRequest.new(defer)
+
+ res0 = req.get("/")
+ session = (cookie = res0["Set-Cookie"])[@session_match]
+ res0.body.should == '{"counter"=>1}'
+ @session_class.all.size.should == 1
+
+ res1 = req.get("/", "HTTP_COOKIE" => cookie)
+ res1["Set-Cookie"][@session_match].should == session
+ res1.body.should == '{"counter"=>2}'
+ @session_class.all.size.should == 1
+
+ res2 = dreq.get("/", "HTTP_COOKIE" => cookie)
+ res2["Set-Cookie"].should be_nil
+ res2.body.should == '{"counter"=>3}'
+ @session_class.all.size.should == 1
+
+ res3 = req.get("/", "HTTP_COOKIE" => cookie)
+ res3["Set-Cookie"][@session_match].should == session
+ res3.body.should == '{"counter"=>4}'
+ @session_class.all.size.should == 1
+ end
+
+ # anyone know how to do this better?
+ it "should merge sessions with multithreading on" do
+ next unless $DEBUG
+ warn 'Running multithread tests for Session::Pool'
+ pool = DataMapper::Session::Datamapper.new(@incrementor, options)
+ req = Rack::MockRequest.new(pool)
+
+ res = req.get('/')
+ res.body.should == '{"counter"=>1}'
+ cookie = res["Set-Cookie"]
+ sess_id = cookie[/#{pool.key}=([^,;]+)/,1]
+
+ delta_incrementor = lambda do |env|
+ # emulate disconjoinment of threading
+ env['rack.session'] = env['rack.session'].dup
+ Thread.stop
+ env['rack.session'][(Time.now.usec*rand).to_i] = true
+ @incrementor.call(env)
+ end
+ tses = Rack::Utils::Context.new pool, delta_incrementor
+ treq = Rack::MockRequest.new(tses)
+ tnum = rand(7).to_i+5
+ r = Array.new(tnum) do
+ Thread.new(treq) do |run|
+ run.get('/', "HTTP_COOKIE" => cookie, 'rack.multithread' => true)
+ end
+ end.reverse.map{|t| t.run.join.value }
+ r.each do |res|
+ res['Set-Cookie'].should == cookie
+ res.body.include('"counter"=>2').should be_true
+ end
+
+ session = @session_class.get(sess_id)
+ session.size.should == tnum+1 # counter
+ session['counter'].should == 2 # meeeh
+ end
+ end
+end
View
46 spec/identity_maps_spec.rb
@@ -0,0 +1,46 @@
+require 'pathname'
+require Pathname(__FILE__).dirname.expand_path + 'spec_helper'
+
+describe DataMapper::IdentityMaps do
+
+ class Name
+ include DataMapper::Resource
+
+ property :id, Serial
+ property :name, String
+ end
+
+ DataMapper.auto_migrate!
+
+ class App
+ def initialize(status = 200, headers = "", response = "", &block)
+ @status, @headers, @response, @block = status, headers, response, block
+ end
+
+ def call(env)
+ @block.call
+ [@status, @headers, @response]
+ end
+ end
+
+ after :each do
+ Name.all.destroy!
+ end
+
+ it 'should collect resources loaded from the datasource' do
+ app = App.new do
+ Name.create(:name => 'first')
+ repository.identity_map(Name).size.should == 1
+ Name.create(:name => 'second')
+ repository.identity_map(Name).size.should == 2
+ Name.create(:name => 'third')
+ repository.identity_map(Name).size.should == 3
+ end
+ DataMapper::IdentityMaps.new(app).call(nil)
+
+ Name.all.size.should == 3
+
+ repository.identity_map(Name).size.should == 0
+ end
+
+end
View
112 spec/restful_transactions_spec.rb
@@ -0,0 +1,112 @@
+require 'pathname'
+require Pathname(__FILE__).dirname.expand_path + 'spec_helper'
+
+
+describe DataMapper::RestfulTransactions do
+
+ class Name
+ include DataMapper::Resource
+
+ property :id, Serial
+ property :name, String
+ end
+
+ DataMapper.auto_migrate!
+
+ class RestfulTransactionsApp
+ def initialize(status, headers = "", response = "", &block)
+ @status, @headers, @response, @block = status, headers, response, block
+ end
+
+ def call(env)
+ @block.call
+ [@status, @headers, @response]
+ end
+ end
+ def mock_request(stubs={})
+ @mock_request ||= mock(::Rack::Request, stubs)
+ end
+
+ before :each do
+ ::Rack::Request.stub!(:new).with(nil).and_return(mock_request)
+ end
+
+ after :each do
+ Name.all.destroy!
+ end
+
+ it 'should commit on redirects unless it is GET request' do
+ mock_request.should_receive(:request_method).any_number_of_times.and_return("POST")
+ app = RestfulTransactionsApp.new(301) do
+ Name.create(:name => 'first')
+ end
+ DataMapper::RestfulTransactions.new(app).call(nil)
+ Name.all.size.should == 1
+ Name.first.name.should == 'first'
+
+ app = App.new(302) do
+ Name.create(:name => 'second')
+ end
+ DataMapper::RestfulTransactions.new(app).call(nil)
+ Name.all.size.should == 2
+ Name.all.last.name.should == 'second'
+
+ app = RestfulTransactionsApp.new(303) do
+ Name.create(:name => 'third')
+ end
+ DataMapper::RestfulTransactions.new(app).call(nil)
+ Name.all.size.should == 3
+ Name.all.last.name.should == 'third'
+
+ app = RestfulTransactionsApp.new(307) do
+ Name.create(:name => 'forth')
+ end
+ DataMapper::RestfulTransactions.new(app).call(nil)
+ Name.all.size.should == 4
+ Name.all.last.name.should == 'forth'
+ end
+
+ it 'should have no transaction on GET requests' do
+ mock_request.should_receive(:request_method).any_number_of_times.and_return("GET")
+ app = RestfulTransactionsApp.new(200) do
+ Name.create(:name => 'first')
+ end
+ DataMapper::RestfulTransactions.new(app).call(nil)
+ Name.all.size.should == 1
+
+ app = RestfulTransactionsApp.new(500) do
+ Name.create(:name => 'second')
+ raise "error"
+ end
+ lambda { DataMapper::RestfulTransactions.new(app).call(nil) }.should raise_error
+ Name.all.size.should == 2
+ end
+
+ it 'should rollback when status is not redirect and method is not GET' do
+ mock_request.should_receive(:request_method).any_number_of_times.and_return("PUT")
+ app = RestfulTransactionsApp.new(200) do
+ Name.create(:name => 'first')
+ # TODO is this read uncommited ?
+ Name.all.size.should == 1
+ end
+ DataMapper::RestfulTransactions.new(app).call(nil)
+ Name.all.size.should == 0
+
+ app = RestfulTransactionsApp.new(404) do
+ Name.create(:name => 'first')
+ # TODO is this read uncommited ?
+ Name.all.size.should == 1
+ end
+ DataMapper::RestfulTransactions.new(app).call(nil)
+ Name.all.size.should == 0
+
+ app = RestfulTransactionsApp.new(503) do
+ Name.create(:name => 'first')
+ # TODO is this read uncommited ?
+ Name.all.size.should == 1
+ end
+ DataMapper::RestfulTransactions.new(app).call(nil)
+ Name.all.size.should == 0
+ end
+
+end
View
1  spec/spec.opts
@@ -0,0 +1 @@
+--colour
View
27 spec/spec_helper.rb
@@ -0,0 +1,27 @@
+require 'pathname'
+require 'rubygems'
+
+gem 'rspec', '~>1.2'
+require 'spec'
+
+$LOAD_PATH << Pathname(__FILE__).dirname.parent.expand_path + 'lib'
+require 'rack_datamapper'
+
+def load_driver(name, default_uri)
+ return false if ENV['ADAPTER'] != name.to_s
+
+ begin
+ DataMapper.setup(name, ENV["#{name.to_s.upcase}_SPEC_URI"] || default_uri)
+ DataMapper::Repository.adapters[:default] = DataMapper::Repository.adapters[name]
+ true
+ rescue LoadError => e
+ warn "Could not load do_#{name}: #{e}"
+ false
+ end
+end
+
+ENV['ADAPTER'] ||= 'sqlite3'
+
+HAS_SQLITE3 = load_driver(:sqlite3, 'sqlite3::memory:')
+HAS_MYSQL = load_driver(:mysql, 'mysql://localhost/dm_core_test')
+HAS_POSTGRES = load_driver(:postgres, 'postgres://postgres@localhost/dm_core_test')
View
86 spec/transaction_boundaries_spec.rb
@@ -0,0 +1,86 @@
+require 'pathname'
+require Pathname(__FILE__).dirname.expand_path + 'spec_helper'
+
+describe DataMapper::TransactionBoundaries do
+
+ class Name
+ include DataMapper::Resource
+
+ property :id, Serial
+ property :name, String
+ end
+
+ DataMapper.auto_migrate!
+
+ class TransactionBoundariesApp
+ def initialize(status, headers = "", response = "", &block)
+ @status, @headers, @response, @block = status, headers, response, block
+ end
+
+ def call(env)
+ @block.call
+ [@status, @headers, @response]
+ end
+ end
+
+ after :each do
+ Name.all.destroy!
+ end
+
+ it 'should commit on status < 400 and status >= 200' do
+ app = TransactionBoundariesApp.new(301) do
+ Name.create(:name => 'first')
+ end
+ DataMapper::TransactionBoundaries.new(app).call(nil)
+ Name.all.size.should == 1
+ Name.first.name.should == 'first'
+
+ app = TransactionBoundariesApp.new(200) do
+ Name.create(:name => 'second')
+ end
+ DataMapper::TransactionBoundaries.new(app).call(nil)
+ Name.all.size.should == 2
+ Name.all.last.name.should == 'second'
+
+ app = TransactionBoundariesApp.new(303) do
+ Name.create(:name => 'third')
+ end
+ DataMapper::TransactionBoundaries.new(app).call(nil)
+ Name.all.size.should == 3
+ Name.all.last.name.should == 'third'
+
+ app = TransactionBoundariesApp.new(222) do
+ Name.create(:name => 'forth')
+ end
+ DataMapper::TransactionBoundaries.new(app).call(nil)
+ Name.all.size.should == 4
+ Name.all.last.name.should == 'forth'
+ end
+
+ it 'should rollback on status < 200 or status >= 400' do
+ app = TransactionBoundariesApp.new(100) do
+ Name.create(:name => 'first')
+ # TODO is this read uncommited ?
+ Name.all.size.should == 1
+ end
+ DataMapper::TransactionBoundaries.new(app).call(nil)
+ Name.all.size.should == 0
+
+ app = TransactionBoundariesApp.new(404) do
+ Name.create(:name => 'first')
+ # TODO is this read uncommited ?
+ Name.all.size.should == 1
+ end
+ DataMapper::TransactionBoundaries.new(app).call(nil)
+ Name.all.size.should == 0
+
+ app = TransactionBoundariesApp.new(500) do
+ Name.create(:name => 'first')
+ # TODO is this read uncommited ?
+ Name.all.size.should == 1
+ end
+ DataMapper::TransactionBoundaries.new(app).call(nil)
+ Name.all.size.should == 0
+ end
+
+end
Please sign in to comment.
Something went wrong with that request. Please try again.