granite 0.8.3 → 0.9.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/lib/granite/action/exceptions_handling.rb +39 -0
- data/lib/granite/action/performing.rb +6 -26
- data/lib/granite/action/transaction.rb +43 -22
- data/lib/granite/action/transaction_manager.rb +84 -0
- data/lib/granite/action/transaction_manager/transactions_stack.rb +65 -0
- data/lib/granite/version.rb +1 -1
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5d965fa31280c0c7068dec41cd034c2242444690
|
4
|
+
data.tar.gz: 22680daf024cf9d8956715e40c75ce2d3ad4cf93
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3c92b2d9fb29164aa10c6b390686cdfca6830861eccaa8c85b6986996ee768fe0e34a7b6219ff4d6ae9a384d4d2cfd514a2896b248c9f01eff2af9bf0eef361b
|
7
|
+
data.tar.gz: 0f5fed5af5c2d707a41e3de1d4d18695e464605e0cd31fbbe0526e23c982fc5a02f1ea5961a82367baf06119994006af76f343ae271129575f9f0fced2666d1f
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Granite
|
2
|
+
class Action
|
3
|
+
module ExceptionsHandling
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
class_attribute :_exception_handlers, instance_writer: false
|
8
|
+
self._exception_handlers = {}
|
9
|
+
|
10
|
+
protected :_exception_handlers # rubocop:disable Style/AccessModifierDeclarations
|
11
|
+
end
|
12
|
+
|
13
|
+
module ClassMethods
|
14
|
+
# Register default handler for exceptions thrown inside execute_perform! and after_commit methods.
|
15
|
+
# @param klass Exception class, could be parent class too [Class]
|
16
|
+
# @param block [Block<Exception>] with default behavior for handling specified
|
17
|
+
# type exceptions. First block argument is raised exception instance.
|
18
|
+
#
|
19
|
+
# @return [Hash<Class, Proc>] Registered handlers
|
20
|
+
def handle_exception(klass, &block)
|
21
|
+
self._exception_handlers = _exception_handlers.merge(klass => block)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def handled_exceptions
|
28
|
+
_exception_handlers.keys
|
29
|
+
end
|
30
|
+
|
31
|
+
def handle_exception(e)
|
32
|
+
klass = e.class.ancestors.detect do |ancestor|
|
33
|
+
ancestor <= Exception && _exception_handlers[ancestor]
|
34
|
+
end
|
35
|
+
instance_exec(e, &_exception_handlers[klass]) if klass
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'granite/action/exceptions_handling'
|
1
2
|
require 'granite/action/transaction'
|
2
3
|
require 'granite/action/error'
|
3
4
|
|
@@ -17,28 +18,14 @@ module Granite
|
|
17
18
|
module Performing
|
18
19
|
extend ActiveSupport::Concern
|
19
20
|
|
21
|
+
include ExceptionsHandling
|
20
22
|
include Transaction
|
21
23
|
|
22
24
|
included do
|
23
|
-
class_attribute :_exception_handlers, instance_writer: false
|
24
|
-
self._exception_handlers = {}
|
25
|
-
|
26
|
-
protected :_exception_handlers
|
27
|
-
|
28
25
|
define_callbacks :execute_perform
|
29
26
|
end
|
30
27
|
|
31
28
|
module ClassMethods
|
32
|
-
# Register default handler for exceptions thrown inside execute_perform! method.
|
33
|
-
# @param klass Exception class, could be parent class too [Class]
|
34
|
-
# @param block [Block<Exception>] with default behavior for handling specified
|
35
|
-
# type exceptions. First block argument is raised exception instance.
|
36
|
-
#
|
37
|
-
# @return [Hash<Class, Proc>] Registered handlers
|
38
|
-
def handle_exception(klass, &block)
|
39
|
-
self._exception_handlers = _exception_handlers.merge(klass => block)
|
40
|
-
end
|
41
|
-
|
42
29
|
def perform(*)
|
43
30
|
fail 'Perform block declaration was removed! Please declare `private def execute_perform!(*)` method'
|
44
31
|
end
|
@@ -54,7 +41,7 @@ module Granite
|
|
54
41
|
# using `:on`)
|
55
42
|
# @return [Object] result of execute_perform! method execution or false in case of errors
|
56
43
|
def perform(context: nil, **options)
|
57
|
-
|
44
|
+
transaction do
|
58
45
|
valid?(context) && perform_action(options)
|
59
46
|
end
|
60
47
|
end
|
@@ -72,7 +59,7 @@ module Granite
|
|
72
59
|
# @raise [Granite::Action::ValidationError] Action or associated objects are invalid
|
73
60
|
# @raise [NotImplementedError] execute_perform! method was not defined yet
|
74
61
|
def perform!(context: nil, **options)
|
75
|
-
|
62
|
+
transaction do
|
76
63
|
validate!(context)
|
77
64
|
perform_action!(**options)
|
78
65
|
end
|
@@ -88,7 +75,7 @@ module Granite
|
|
88
75
|
# @raise [NotImplementedError] execute_perform! method was not defined yet
|
89
76
|
def try_perform!(context: nil, **options)
|
90
77
|
return unless satisfy_preconditions?
|
91
|
-
|
78
|
+
transaction do
|
92
79
|
validate!(context)
|
93
80
|
perform_action!(**options)
|
94
81
|
end
|
@@ -108,7 +95,7 @@ module Granite
|
|
108
95
|
result = run_callbacks(:execute_perform) { execute_perform!(options) }
|
109
96
|
@_action_performed = true
|
110
97
|
result || true
|
111
|
-
rescue *
|
98
|
+
rescue *handled_exceptions => e
|
112
99
|
handle_exception(e)
|
113
100
|
raise_validation_error(e) if raise_errors
|
114
101
|
raise Rollback
|
@@ -121,13 +108,6 @@ module Granite
|
|
121
108
|
def execute_perform!(**_options)
|
122
109
|
fail NotImplementedError, "BA perform body MUST be defined for #{self}"
|
123
110
|
end
|
124
|
-
|
125
|
-
def handle_exception(e)
|
126
|
-
klass = e.class.ancestors.detect do |ancestor|
|
127
|
-
ancestor <= Exception && _exception_handlers[ancestor]
|
128
|
-
end
|
129
|
-
instance_exec(e, &_exception_handlers[klass]) if klass
|
130
|
-
end
|
131
111
|
end
|
132
112
|
end
|
133
113
|
end
|
@@ -1,39 +1,60 @@
|
|
1
|
+
require 'granite/action/transaction_manager'
|
2
|
+
|
1
3
|
module Granite
|
2
4
|
class Action
|
3
|
-
class Rollback < defined?(ActiveRecord) ? ActiveRecord::Rollback : StandardError
|
4
|
-
end
|
5
|
-
|
6
5
|
module Transaction
|
7
6
|
extend ActiveSupport::Concern
|
8
7
|
|
9
|
-
|
8
|
+
included do
|
9
|
+
define_callbacks :commit
|
10
|
+
end
|
10
11
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
12
|
+
module ClassMethods
|
13
|
+
delegate :transaction, to: :'Granite::Action::TransactionManager'
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
# Defines a callback which will be triggered right after transaction committed
|
18
|
+
# Uses the same arguments as `ActiveSupport::Callbacks.set_callback` except for the first two
|
19
|
+
#
|
20
|
+
def after_commit(*args, &block)
|
21
|
+
set_callback :commit, :after, *args, &block
|
19
22
|
end
|
20
23
|
end
|
21
24
|
|
22
|
-
def
|
23
|
-
|
24
|
-
|
25
|
+
def run_callbacks(event)
|
26
|
+
if event.to_s == 'commit'
|
27
|
+
begin
|
28
|
+
super event
|
29
|
+
rescue *handled_exceptions => e
|
30
|
+
handle_exception(e)
|
31
|
+
end
|
32
|
+
else
|
33
|
+
super event
|
34
|
+
end
|
25
35
|
end
|
26
36
|
|
37
|
+
private
|
38
|
+
|
39
|
+
attr_accessor :in_transaction
|
40
|
+
|
27
41
|
def transaction(&block)
|
28
|
-
if
|
29
|
-
|
42
|
+
if in_transaction
|
43
|
+
yield
|
30
44
|
else
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
45
|
+
run_in_transaction(&block)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def run_in_transaction
|
50
|
+
self.in_transaction = true
|
51
|
+
|
52
|
+
TransactionManager.transaction do
|
53
|
+
TransactionManager.after_commit(self)
|
54
|
+
yield
|
36
55
|
end
|
56
|
+
ensure
|
57
|
+
self.in_transaction = false
|
37
58
|
end
|
38
59
|
end
|
39
60
|
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'granite/action/transaction_manager/transactions_stack'
|
2
|
+
|
3
|
+
module Granite
|
4
|
+
class Action
|
5
|
+
class Rollback < defined?(ActiveRecord) ? ActiveRecord::Rollback : StandardError
|
6
|
+
end
|
7
|
+
|
8
|
+
module TransactionManager
|
9
|
+
class << self
|
10
|
+
# Runs a block in a transaction
|
11
|
+
# It will open a new transaction or append a block to the current one if it exists
|
12
|
+
# @return [Object] result of a block
|
13
|
+
def transaction(&block)
|
14
|
+
run_in_transaction(&block) || false
|
15
|
+
ensure
|
16
|
+
finish_root_transaction if transactions_stack.depth.zero?
|
17
|
+
end
|
18
|
+
|
19
|
+
# Adds a block or listener object to be executed after finishing the current transaction.
|
20
|
+
# Callbacks are reset after each transaction.
|
21
|
+
# @param [Object] listener an object which will receive `run_callbacks(:commit)` after transaction committed
|
22
|
+
# @param [Proc] block a block which will be called after transaction committed
|
23
|
+
def after_commit(listener = nil, &block)
|
24
|
+
callback = listener || block
|
25
|
+
|
26
|
+
fail 'Block or object is required to register after_commit hook!' unless callback
|
27
|
+
|
28
|
+
transactions_stack.add_callback callback
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
TRANSACTIONS_STACK_KEY = :granite_transaction_manager_transactions_stack
|
34
|
+
|
35
|
+
def transactions_stack
|
36
|
+
Thread.current[TRANSACTIONS_STACK_KEY] ||= TransactionsStack.new
|
37
|
+
end
|
38
|
+
|
39
|
+
def transactions_stack=(value)
|
40
|
+
Thread.current[TRANSACTIONS_STACK_KEY] = value
|
41
|
+
end
|
42
|
+
|
43
|
+
def run_in_transaction(&block)
|
44
|
+
if defined?(ActiveRecord::Base)
|
45
|
+
ActiveRecord::Base.transaction(requires_new: true) do
|
46
|
+
transactions_stack.transaction(&block)
|
47
|
+
end
|
48
|
+
else
|
49
|
+
transactions_stack.transaction(&block)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def finish_root_transaction
|
54
|
+
trigger_after_commit_callbacks
|
55
|
+
ensure
|
56
|
+
self.transactions_stack = nil
|
57
|
+
end
|
58
|
+
|
59
|
+
def trigger_after_commit_callbacks
|
60
|
+
collected_errors = []
|
61
|
+
|
62
|
+
transactions_stack.callbacks.reverse_each do |callback|
|
63
|
+
begin
|
64
|
+
callback.respond_to?(:run_callbacks) ? callback.run_callbacks(:commit) : callback.call
|
65
|
+
rescue StandardError => e
|
66
|
+
collected_errors << e
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
return unless collected_errors.any?
|
71
|
+
|
72
|
+
log_errors(collected_errors[1..-1])
|
73
|
+
fail collected_errors.first
|
74
|
+
end
|
75
|
+
|
76
|
+
def log_errors(errors)
|
77
|
+
errors.each do |error|
|
78
|
+
ActiveData.config.logger.error "Unhandled error in callback: #{error.inspect}\n#{error.backtrace.join("\n")}"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module Granite
|
2
|
+
class Action
|
3
|
+
module TransactionManager
|
4
|
+
# A class to manage transaction callbacks stack.
|
5
|
+
class TransactionsStack
|
6
|
+
attr_reader :depth
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@callbacks = []
|
10
|
+
@depth = 0
|
11
|
+
end
|
12
|
+
|
13
|
+
def transaction
|
14
|
+
start_new!
|
15
|
+
result = yield
|
16
|
+
finish_current!
|
17
|
+
result
|
18
|
+
rescue StandardError, ScriptError
|
19
|
+
rollback_current!
|
20
|
+
raise
|
21
|
+
end
|
22
|
+
|
23
|
+
def add_callback(callback)
|
24
|
+
fail 'Start a transaction before you add callbacks on it' if depth.zero?
|
25
|
+
|
26
|
+
@callbacks.last << callback
|
27
|
+
end
|
28
|
+
|
29
|
+
def callbacks
|
30
|
+
@callbacks.flatten
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def start_new!
|
36
|
+
@depth += 1
|
37
|
+
@callbacks << []
|
38
|
+
end
|
39
|
+
|
40
|
+
def finish_current!
|
41
|
+
finish_current(true)
|
42
|
+
end
|
43
|
+
|
44
|
+
def rollback_current!
|
45
|
+
finish_current(false)
|
46
|
+
end
|
47
|
+
|
48
|
+
def finish_current(result)
|
49
|
+
fail ArgumentError, 'No current transaction' if @depth.zero?
|
50
|
+
|
51
|
+
@depth -= 1
|
52
|
+
|
53
|
+
if result
|
54
|
+
current = @callbacks.pop
|
55
|
+
previous = @callbacks.pop
|
56
|
+
@callbacks << [previous, current].flatten.compact
|
57
|
+
else
|
58
|
+
@callbacks.pop
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
data/lib/granite/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: granite
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.9.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Arkadiy Zabazhanov & friends
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-09-
|
11
|
+
date: 2018-09-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: actionpack
|
@@ -280,6 +280,7 @@ files:
|
|
280
280
|
- lib/granite.rb
|
281
281
|
- lib/granite/action.rb
|
282
282
|
- lib/granite/action/error.rb
|
283
|
+
- lib/granite/action/exceptions_handling.rb
|
283
284
|
- lib/granite/action/performer.rb
|
284
285
|
- lib/granite/action/performing.rb
|
285
286
|
- lib/granite/action/policies.rb
|
@@ -292,6 +293,8 @@ files:
|
|
292
293
|
- lib/granite/action/projectors.rb
|
293
294
|
- lib/granite/action/subject.rb
|
294
295
|
- lib/granite/action/transaction.rb
|
296
|
+
- lib/granite/action/transaction_manager.rb
|
297
|
+
- lib/granite/action/transaction_manager/transactions_stack.rb
|
295
298
|
- lib/granite/action/types.rb
|
296
299
|
- lib/granite/action/types/collection.rb
|
297
300
|
- lib/granite/base.rb
|