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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a80b109dbceea5353225c682dac91eb5c3bc18ed
4
- data.tar.gz: c995edf2a35d481bf68efa13c58abcd30afe876b
3
+ metadata.gz: 5d965fa31280c0c7068dec41cd034c2242444690
4
+ data.tar.gz: 22680daf024cf9d8956715e40c75ce2d3ad4cf93
5
5
  SHA512:
6
- metadata.gz: ff37abbb9cd3326be35840912a9d4ec0fd3b107bccd95465a51925101897c17e2cc05a5378f2d262327e061f4b9d26924b0b1f79063d3b38da5e6e517328f82e
7
- data.tar.gz: 81f809088a68c5769c901bdfbb4ed5d8e93ec1f3148b0e1886d8e82c70cf92efdb669a8f6c6cca85f9515b9ae66d71ba60b06b19b47cb09f6ec35cde8d3a8a86
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
- transactional do
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
- transactional do
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
- transactional do
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 *_exception_handlers.keys => e
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
- private
8
+ included do
9
+ define_callbacks :commit
10
+ end
10
11
 
11
- def transactional(&block)
12
- if transactional?
13
- yield
14
- else
15
- @_transactional = true
16
- result = transaction(&block) || false
17
- @_transactional = nil
18
- result
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 transactional?
23
- # Fuck the police!
24
- !(!@_transactional)
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 defined?(ActiveRecord::Base)
29
- ActiveRecord::Base.transaction(&block)
42
+ if in_transaction
43
+ yield
30
44
  else
31
- begin
32
- yield
33
- rescue Granite::Action::Rollback
34
- false
35
- end
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
@@ -1,3 +1,3 @@
1
1
  module Granite
2
- VERSION = '0.8.3'.freeze
2
+ VERSION = '0.9.0'.freeze
3
3
  end
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.8.3
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-05 00:00:00.000000000 Z
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