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 +4 -4
- data/README.md +54 -16
- data/lib/rack/transaction.rb +21 -15
- data/lib/rack/transaction/configuration.rb +76 -0
- data/lib/rack/transaction/version.rb +5 -0
- data/rack-transaction.gemspec +1 -1
- data/spec/rack/transaction/configuration_spec.rb +178 -0
- data/spec/rack/transaction_spec.rb +16 -47
- data/spec/spec_helper.rb +1 -0
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9de5a130671b3c2f6b5d8c8759041bf5cf02ac4a
|
4
|
+
data.tar.gz: 3781fdb23e87f0e354cf15470894ad864561ef19
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
26
|
-
|
27
|
-
|
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
|
-
|
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
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
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
|
-
|
44
|
-
|
45
|
-
|
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
|
data/lib/rack/transaction.rb
CHANGED
@@ -1,40 +1,46 @@
|
|
1
|
+
require 'rack/transaction/version'
|
2
|
+
require 'rack/transaction/configuration'
|
3
|
+
|
1
4
|
module Rack
|
2
5
|
class Transaction
|
3
|
-
|
6
|
+
attr_reader :config
|
4
7
|
|
5
|
-
def initialize(inner,
|
8
|
+
def initialize(inner, configuration=nil, &block)
|
6
9
|
@inner = inner
|
7
|
-
@
|
8
|
-
|
9
|
-
|
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
|
-
|
21
|
+
config.provider.transaction do
|
17
22
|
result = @inner.call(env)
|
18
|
-
rollback
|
23
|
+
rollback unless successful?(env, *result)
|
19
24
|
end
|
20
25
|
result
|
21
26
|
end
|
22
27
|
|
23
28
|
private
|
24
29
|
|
25
|
-
def
|
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
|
-
|
37
|
+
config.successful?(response, env)
|
28
38
|
end
|
29
39
|
|
30
40
|
def rollback
|
31
|
-
|
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
|
data/rack-transaction.gemspec
CHANGED
@@ -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(:
|
10
|
-
let(:
|
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,
|
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
|
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
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
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
|
data/spec/spec_helper.rb
CHANGED
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.
|
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-
|
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
|