rack-transaction 0.1.0 → 0.2.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7344d9a199dd181db1cef5252d21aa6b4b11ff51
4
- data.tar.gz: 4e50793aef557b46c300ce3f0fa2940ae8af2d93
3
+ metadata.gz: 9de5a130671b3c2f6b5d8c8759041bf5cf02ac4a
4
+ data.tar.gz: 3781fdb23e87f0e354cf15470894ad864561ef19
5
5
  SHA512:
6
- metadata.gz: db561213fd61612450a54cfb64a5ca4905d22e5d01210e7a0b7be7ae9094f1ae9606e321a4d12288641938c040a31690374e0a4c704854bd7093f78e55654ac6
7
- data.tar.gz: 2d91aa10d58c39b847c99543af4ef29f05b517d8aeb9de4f439ebfe1a5f327efe114620a39a01f96bbae41091372ad614aec704bac88ce14052a9ae3a22968d3
6
+ metadata.gz: 634286289f23079d01c71a22f1be081620db2526db1ce0a8f1840826ed1001aab820b67c0d0602fd4ee5d7875973ebf38951aca6d73b5e8a16f08ca124675039
7
+ data.tar.gz: 95b539b35d3106bb46282b8c2c6af403572aaf89fce5b8a3fc50c76455566268fd5551c3b50bd5dd6f07e91ffed324a55c7f62578c37a8ba08f8d75330f6a6fc
data/README.md CHANGED
@@ -22,29 +22,67 @@ gem install rack-transaction
22
22
  Add the following:
23
23
 
24
24
  ```ruby
25
- use Rack::Transaction,
26
- provider: Sequel.connect('sqlite:///') #required
27
- rollback: Sequel::Rollback #required (it also accepts the string version of the constant)
25
+ config = Rack::Transaction::Configuration.new do
26
+ provided_by Sequel.connect('sqlite:///') #required
27
+ rollback_with Sequel::Rollback #required (it also accepts the string version of the constant)
28
+ end
29
+
30
+ use Rack::Transaction, config
31
+ ```
32
+
33
+ or, with the shorthand form:
34
+
35
+ ```ruby
36
+ use Rack::Transaction do
37
+ provided_by Sequel.connect('sqlite:///') #required
38
+ rollback_with Sequel::Rollback #required (it also accepts the string version of the constant)
39
+ end
40
+ ```
41
+
42
+ Do note that `rollback_with` will use the type specified to raise an error,
43
+ which in turn, causes the transaction to rollback. Depending on the provider,
44
+ passed into `provided_by`, providing the transaction, you may want to specify
45
+ an error type provided by the library being used to allow for more graceful
46
+ error handling. For example, Sequel has `Sequel::Rollback` and ActiveRecord has
47
+ `ActiveRecord::Rollback`.
48
+
49
+ It also supports an optional callback to validate that an action was
50
+ successful, even if it wasn't recognized as a client or server error. For
51
+ example, Sinatra sets `sinatra.error` on the `env` in the event of an error, so
52
+ we'll probably want to rollback. We can specify the validation callback with
53
+ `ensure_success_with`. The callback will have the `Rack::Response` and `env`
54
+ passed to it as arguments.
55
+
56
+ ```ruby
57
+ use Rack::Transaction do
58
+ provided_by Sequel.connect('sqlite:///')
59
+ rollback_with Sequel::Rollback
60
+ ensure_success_with { |response, env| env['sinatra.error'] }
28
61
  ```
29
62
 
30
- Do note that `:rollback` will use the type specified to raise an error, which
31
- in turn, causes the transaction to rollback. Depending on the `:provider`
32
- providing the transaction, you may want to specify an error type provided by
33
- the library being used to allow for more graceful error handling. For example,
34
- Sequel has `Sequel::Rollback` and ActiveRecord has `ActiveRecord::Rollback`.
63
+ ### Configuration
35
64
 
36
- It also supports an optional error callback to check for errors in the
37
- environment outside of the normal client or server errors. For example, Sinatra
38
- sets `sinatra.error` on the `env` in the event of an error, so we'll probably
39
- want to rollback. We can check for this by specifying the `:error` setting.
65
+ By default, `Rack::Transaction` will wrap all requests except for request of
66
+ methods GET, HEAD, & OPTION. It also provides away to include or exclude
67
+ certain requests from participating in the transaction. This is done by passing
68
+ in a block to `include` or `exclude` to the configuration. The block can expect
69
+ a `Rack::Request` instance to be used to determine whether or not to include or
70
+ exclude said request. For example, you may want to wrap a GET request in a
71
+ transaction, because the action it routes to is handling the final leg of
72
+ OAauth authorization and saving the refresh token as well as other business
73
+ logic.
40
74
 
41
75
  ```ruby
42
- use Rack::Transaction,
43
- provider: Sequel.connect('sqlite:///')
44
- rollback: Sequel::Rollback
45
- error: ->(env){ env['sinatra.error'] }
76
+ use Rack::Transaction do
77
+ // other config stuff
78
+
79
+ include { |request| request.path =~ %r{/oauth/callback$}i }
80
+ exclude { |request| request.path =~ %r{/search$}i }
81
+ end
46
82
  ```
47
83
 
84
+ Do note that includes take priority over any excludes specified.
85
+
48
86
  ## Contributing
49
87
 
50
88
  1. Fork it
@@ -1,40 +1,46 @@
1
+ require 'rack/transaction/version'
2
+ require 'rack/transaction/configuration'
3
+
1
4
  module Rack
2
5
  class Transaction
3
- VERSION = "0.1.0".freeze
6
+ attr_reader :config
4
7
 
5
- def initialize(inner, settings)
8
+ def initialize(inner, configuration=nil, &block)
6
9
  @inner = inner
7
- @provider = settings.fetch(:provider)
8
- @rollback = settings.fetch(:rollback)
9
- @error = settings[:error]
10
+ @config = configuration || Configuration.new
11
+
12
+ config.setup(&block)
10
13
  end
11
14
 
12
15
  def call(env)
16
+ config.validate!
17
+
13
18
  return @inner.call(env) unless use_transaction?(env)
14
19
 
15
20
  result = nil
16
- @provider.transaction do
21
+ config.provider.transaction do
17
22
  result = @inner.call(env)
18
- rollback if has_error?(env, *result)
23
+ rollback unless successful?(env, *result)
19
24
  end
20
25
  result
21
26
  end
22
27
 
23
28
  private
24
29
 
25
- def has_error?(env, status, headers, body)
30
+ def use_transaction?(env)
31
+ request = Request.new env
32
+ config.accepts?(request)
33
+ end
34
+
35
+ def successful?(env, status, headers, body)
26
36
  response = Response.new body, status, headers
27
- response.client_error? || response.server_error? || (@error.respond_to?(:call) && @error.call(env))
37
+ config.successful?(response, env)
28
38
  end
29
39
 
30
40
  def rollback
31
- klass = @rollback.is_a?(String) ? Object.const_get(@rollback) : @rollback
41
+ error = config.rollback_error
42
+ klass = error.is_a?(String) ? Object.const_get(error) : error
32
43
  raise klass
33
44
  end
34
-
35
- def use_transaction?(env)
36
- request = Request.new env
37
- !(request.get? || request.head? || request.options?)
38
- end
39
45
  end
40
46
  end
@@ -0,0 +1,76 @@
1
+ module Rack
2
+ class Transaction
3
+ class Configuration
4
+ class Invalid < StandardError; end
5
+ class InvalidProvider < StandardError; end
6
+ class InvalidRollbackError < StandardError; end
7
+ class InvalidResponseValidation < StandardError; end
8
+
9
+ attr_reader :provider, :rollback_error, :success_validation
10
+
11
+ def initialize(&block)
12
+ @includers = []
13
+ @excluders = []
14
+ with_defaults
15
+ setup(&block)
16
+ end
17
+
18
+ def setup(&block)
19
+ instance_eval(&block) if block_given?
20
+ end
21
+
22
+ def provided_by(value)
23
+ raise InvalidProvider, 'Provider must respond to transaction' unless value.respond_to?(:transaction)
24
+ @provider = value
25
+ self
26
+ end
27
+
28
+ def rollback_with(value)
29
+ unless (value.is_a?(Class) && (value <= StandardError)) || value.is_a?(String)
30
+ raise InvalidRollbackError, 'Rollback error must be a valid StandardError type'
31
+ end
32
+ @rollback_error = value
33
+ self
34
+ end
35
+
36
+ def ensure_success_with(&validation)
37
+ raise InvalidResponseValidation, 'Response validation must respond to call' unless validation.respond_to?(:call)
38
+ @success_validation = validation
39
+ self
40
+ end
41
+
42
+ def include(&block)
43
+ @includers << block
44
+ self
45
+ end
46
+
47
+ def exclude(&block)
48
+ @excluders << block
49
+ self
50
+ end
51
+
52
+ def accepts?(request)
53
+ @excluders.all?{|x| !x.call(request)} || @includers.any?{|x| x.call(request)}
54
+ end
55
+
56
+ def successful?(response, env)
57
+ !response.client_error? && !response.server_error? && (success_validation.nil? || success_validation.call(response, env))
58
+ end
59
+
60
+ def validate!
61
+ missing = []
62
+ missing << 'provider' unless provider
63
+ missing << 'rollback_error' unless rollback_error
64
+ raise Invalid, "Missing #{missing.join ' & '}" if missing.any?
65
+ end
66
+
67
+ private
68
+
69
+ def with_defaults
70
+ exclude(&:get?)
71
+ exclude(&:head?)
72
+ exclude(&:options?)
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,5 @@
1
+ module Rack
2
+ class Transaction
3
+ VERSION = "0.2.0".freeze
4
+ end
5
+ end
@@ -1,5 +1,5 @@
1
1
  # -*- encoding: utf-8 -*-
2
- require File.expand_path('../lib/rack/transaction', __FILE__)
2
+ require File.expand_path('../lib/rack/transaction/version', __FILE__)
3
3
 
4
4
  Gem::Specification.new do |gem|
5
5
  gem.author = 'vyrak.bunleang@gmail.com'
@@ -0,0 +1,178 @@
1
+ require 'spec_helper'
2
+
3
+ describe Rack::Transaction::Configuration do
4
+ let(:env){ {} }
5
+ subject { Rack::Transaction::Configuration.new }
6
+
7
+ describe "#provided_by" do
8
+ it 'sets provider' do
9
+ provider = Class.new { def self.transaction; end }
10
+ result = subject.provided_by provider
11
+ result.must_equal subject
12
+ subject.provider.must_equal provider
13
+ end
14
+
15
+ it 'raises when provider does not respond to :transaction' do
16
+ proc {
17
+ subject.provided_by Object
18
+ }.must_raise Rack::Transaction::Configuration::InvalidProvider
19
+ end
20
+ end
21
+
22
+ describe "#rollback_with" do
23
+ it 'sets rollback_error StandardError' do
24
+ rollback_error = StandardError
25
+ result = subject.rollback_with rollback_error
26
+ result.must_equal subject
27
+ subject.rollback_error.must_equal rollback_error
28
+ end
29
+
30
+ it 'sets rollback_error StandardError subclass' do
31
+ rollback_error = RuntimeError
32
+ result = subject.rollback_with rollback_error
33
+ result.must_equal subject
34
+ subject.rollback_error.must_equal rollback_error
35
+ end
36
+
37
+ it 'sets rollback_error with String' do
38
+ rollback_error = 'RuntimeError'
39
+ result = subject.rollback_with rollback_error
40
+ result.must_equal subject
41
+ subject.rollback_error.must_equal rollback_error
42
+ end
43
+
44
+ it 'raises when rollback_error not String or StandardError' do
45
+ proc {
46
+ subject.rollback_with Object.new
47
+ }.must_raise Rack::Transaction::Configuration::InvalidRollbackError
48
+ end
49
+ end
50
+
51
+ describe "#ensure_success_with" do
52
+ it 'sets success_validation' do
53
+ callable = nil
54
+ result = subject.ensure_success_with {|response, env| callable = true}
55
+ result.must_equal subject
56
+
57
+ subject.success_validation.call
58
+ callable.must_equal true
59
+ end
60
+
61
+ it 'raises when success_validation does not respond to :call' do
62
+ proc {
63
+ subject.ensure_success_with
64
+ }.must_raise Rack::Transaction::Configuration::InvalidResponseValidation
65
+ end
66
+ end
67
+
68
+ describe '#validate!' do
69
+ let(:provider){ Rack::Transaction::Configuration }
70
+ subject { Rack::Transaction::Configuration }
71
+
72
+ it 'raises when provider not configured' do
73
+ config = subject.new do
74
+ rollback_with(StandardError)
75
+ end
76
+ proc { config.validate! }.must_raise Rack::Transaction::Configuration::Invalid
77
+ end
78
+
79
+ it 'raises when rollback_error not configured' do
80
+ config = subject.new do
81
+ provided_by(Class.new { def self.transaction; end })
82
+ end
83
+ proc { config.validate! }.must_raise Rack::Transaction::Configuration::Invalid
84
+ end
85
+
86
+ it 'wont raise when valid' do
87
+ config = subject.new do
88
+ provided_by(Class.new { def self.transaction; end })
89
+ rollback_with(StandardError)
90
+ end
91
+ config.validate!
92
+ end
93
+ end
94
+
95
+ describe '#successful?' do
96
+ it 'is unsuccessful for a response with a client error' do
97
+ response = Rack::Response.new [], 400, {}
98
+ subject.successful?(response, {}).must_equal false
99
+ end
100
+
101
+ it 'is unsuccessful for a response with a server error' do
102
+ response = Rack::Response.new [], 500, {}
103
+ subject.successful?(response, {}).must_equal false
104
+ end
105
+
106
+ it 'is unsuccessful for a validation error' do
107
+ validation_args = nil
108
+ env = {}
109
+ response = Rack::Response.new [], 200, {}
110
+
111
+ subject.ensure_success_with { |*args| validation_args = args; false }
112
+ subject.successful?(response, env).must_equal false
113
+
114
+
115
+ validation_args.length.must_equal 2
116
+ response_arg, env_arg = validation_args
117
+ response_arg.must_equal response
118
+ env_arg.must_equal env
119
+ end
120
+
121
+ it 'is successful' do
122
+ response = Rack::Response.new [], 200, {}
123
+ subject.successful?(response, {}).must_equal true
124
+ end
125
+ end
126
+
127
+ describe 'with defaults' do
128
+ let(:request){ Rack::Request.new env }
129
+
130
+ it 'accepts request' do
131
+ subject.accepts?(request).must_equal true
132
+ end
133
+
134
+ %w{DELETE POST PUT}.each do |method|
135
+ it "accepts #{method} request" do
136
+ env['REQUEST_METHOD'] = method
137
+ subject.accepts?(request).must_equal true
138
+ end
139
+ end
140
+
141
+ %w{GET HEAD OPTIONS}.each do |method|
142
+ it "wont accept #{method} request" do
143
+ env['REQUEST_METHOD'] = method
144
+ subject.accepts?(request).must_equal false
145
+ end
146
+ end
147
+ end
148
+
149
+ describe 'with inclusion' do
150
+ let(:env){ {'REQUEST_METHOD' => 'GET'} }
151
+ let(:request){ Rack::Request.new env }
152
+
153
+ it 'returns self' do
154
+ result = subject.include(&:get?)
155
+ result.must_equal subject
156
+ end
157
+
158
+ it 'accepts request' do
159
+ subject.include(&:get?)
160
+ subject.accepts?(request).must_equal true
161
+ end
162
+ end
163
+
164
+ describe 'with exclusion' do
165
+ let(:env){ {'REQUEST_METHOD' => 'POST'} }
166
+ let(:request){ Rack::Request.new env }
167
+
168
+ it 'returns self' do
169
+ result = subject.exclude(&:post?)
170
+ result.must_equal subject
171
+ end
172
+
173
+ it 'wont accept excluded request' do
174
+ subject.exclude(&:post?)
175
+ subject.accepts?(request).must_equal false
176
+ end
177
+ end
178
+ end
@@ -1,15 +1,20 @@
1
1
  require 'spec_helper'
2
- require 'rack/transaction'
3
2
 
4
3
  describe Rack::Transaction do
5
4
  let(:inner){ mock }
6
5
  let(:env){ {'field' => 'variable'} }
7
6
  let(:table_name){ :rack }
8
7
  let(:dataset){ connection[table_name] }
9
- let(:rollback){ Sequel::Rollback }
10
- let(:settings){ {provider: connection, rollback: rollback} }
8
+ let(:error){ Sequel::Rollback }
9
+ let(:config){
10
+ context = self
11
+ Rack::Transaction::Configuration.new do
12
+ provided_by context.connection
13
+ rollback_with context.error
14
+ end
15
+ }
11
16
 
12
- subject { Rack::Transaction.new inner, settings }
17
+ subject { Rack::Transaction.new inner, config }
13
18
 
14
19
  before do
15
20
  connection.create_table table_name do
@@ -42,62 +47,26 @@ describe Rack::Transaction do
42
47
  dataset.wont_be :empty?
43
48
  end
44
49
 
45
- it 'rolls back on server error' do
46
- expect_call 500
47
- result = subject.call env
48
- result.must_equal [500, {}, []]
49
- dataset.must_be :empty?
50
- end
51
-
52
- it 'rolls back on client error' do
50
+ it 'rolls back on error' do
53
51
  expect_call 400
54
52
  result = subject.call env
55
53
  result.must_equal [400, {}, []]
56
54
  dataset.must_be :empty?
57
55
  end
58
56
 
59
- it 'rolls back on custom error callback' do
60
- args = nil
61
- settings[:error] = ->(*a){ args = a; true }
62
- expect_call 200
63
- result = subject.call env
64
- result.must_equal [200, {}, []]
65
- dataset.must_be :empty?
66
- args.must_equal [env]
67
- end
68
-
69
57
  it 'rolls back on string rollback' do
70
- settings[:rollback] = "Sequel::Rollback"
71
58
  expect_call 400
59
+ subject.config.rollback_with("Sequel::Rollback")
72
60
  result = subject.call env
73
61
  result.must_equal [400, {}, []]
74
62
  dataset.must_be :empty?
75
63
  end
76
64
 
77
- %w{ GET HEAD OPTIONS }.each do |method|
78
- # shouldn't be modifying anything on these types of requests; modifying for assertion purposes
79
-
80
- describe "on #{method} request" do
81
- before { env['REQUEST_METHOD'] = method }
82
-
83
- it 'wont rollback on custom error callback' do
84
- settings[:error] = ->{ true }
85
- expect_call 200
86
- subject.call env
87
- dataset.wont_be :empty?
88
- end
89
-
90
- it 'wont rollback on server error' do
91
- expect_call 500
92
- subject.call env
93
- dataset.wont_be :empty?
94
- end
95
-
96
- it 'wont rollback on client error' do
97
- expect_call 400
98
- subject.call env
99
- dataset.wont_be :empty?
100
- end
65
+ it 'ensures valid configuration' do
66
+ context = self
67
+ middleware = Rack::Transaction.new inner do
68
+ provided_by context.connection
101
69
  end
70
+ proc { middleware.call env }.must_raise Rack::Transaction::Configuration::Invalid
102
71
  end
103
72
  end
@@ -4,6 +4,7 @@ Bundler.require :development
4
4
  $:.unshift File.expand_path('../../lib', __FILE__)
5
5
  require 'minitest/pride'
6
6
  require 'minitest/autorun'
7
+ require 'rack/transaction'
7
8
 
8
9
  class Minitest::Spec
9
10
  def mock
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rack-transaction
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - vyrak.bunleang@gmail.com
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-02-28 00:00:00.000000000 Z
11
+ date: 2015-03-04 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Middleware for transactions
14
14
  email:
@@ -23,7 +23,10 @@ files:
23
23
  - README.md
24
24
  - Rakefile
25
25
  - lib/rack/transaction.rb
26
+ - lib/rack/transaction/configuration.rb
27
+ - lib/rack/transaction/version.rb
26
28
  - rack-transaction.gemspec
29
+ - spec/rack/transaction/configuration_spec.rb
27
30
  - spec/rack/transaction_spec.rb
28
31
  - spec/spec_helper.rb
29
32
  homepage: https://github.com/p60/rack-transaction
@@ -50,5 +53,6 @@ signing_key:
50
53
  specification_version: 4
51
54
  summary: Middleware for transactions
52
55
  test_files:
56
+ - spec/rack/transaction/configuration_spec.rb
53
57
  - spec/rack/transaction_spec.rb
54
58
  - spec/spec_helper.rb