granite 0.8.3 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
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