axn 0.1.0.pre.alpha.2.5.2 → 0.1.0.pre.alpha.2.5.3.1

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
  SHA256:
3
- metadata.gz: 6902996eda6bf8911d2f90f73217d56a0905ddf0772ebfee3a9c2bbf8072279e
4
- data.tar.gz: 3615981ef31731e017b2b9399b485290041b1a79cc0ccc231c189399ddb4e5f4
3
+ metadata.gz: d67c51bf6cef09cdbfb8246769eefb7c9b1baa3f7d75168d599f898d66d00157
4
+ data.tar.gz: 06d6b82b58b9a65bd1d8da9e68a8a640a5fcf0e1dafdbd5d756ea1d1682f7e95
5
5
  SHA512:
6
- metadata.gz: 1f9ae2c8d65defac13be5abded734bdcf1057e8d880878d8cf4c57c58ef537d257a7a96208fe83616ce8ddc89344c60042b9eadbe8575b52d14fa9fa9981489e
7
- data.tar.gz: bcbf95ea1e179a44076cb5b451f0ab1f94001bccb30bd78fd3fbbd025472fb08bed4cda805cf934ab6f3328f4aced08a96ff4983142aeed67c7121ecddd11e2b
6
+ metadata.gz: a656fe06eec316eb1c974586adec336afe6f09257da62f88789f467fe06f0f3677249fccb1aec7dd83c3328c1cc0ee505640e786b1892338ff0be71723b758c3
7
+ data.tar.gz: 337a92138da80dcf5e00947b4cb00589d689e1dc6dae3340d91e76874e4a335feb3a1ef254c3e6220c6a0d271f49fe8f77f9deea5d731ed1caa3f3c4317c1b42
data/.rubocop.yml CHANGED
@@ -30,6 +30,9 @@ Style/TrailingCommaInHashLiteral:
30
30
  Style/ClassAndModuleChildren:
31
31
  Enabled: false
32
32
 
33
+ Style/HashSyntax:
34
+ EnforcedShorthandSyntax: always
35
+
33
36
  Style/DoubleNegation:
34
37
  Enabled: false
35
38
 
data/CHANGELOG.md CHANGED
@@ -3,6 +3,13 @@
3
3
  ## Unreleased
4
4
  * N/A
5
5
 
6
+ ## 0.1.0-alpha.2.5.3.1
7
+ * Remove explicit 'require rspec' from `axn/testing/spec_helpers` (must already be loaded)
8
+
9
+ ## 0.1.0-alpha.2.5.3
10
+ * More aggressive logging of swallowed exceptions when not in production mode
11
+ * Make automatic pre/post logging more digestible
12
+
6
13
  ## 0.1.0-alpha.2.5.2
7
14
  * [BREAKING] Removing `EnqueueAllInBackground` + `EnqueueAllWorker` - better + simply solved at application level
8
15
  * [TEST] Expose spec helpers to consumers (add `require "axn/testing/spec_helpers"` to your `spec_helper.rb`)
@@ -95,11 +95,11 @@ Accepts `error` and/or `success` keys. Values can be a string (returned directl
95
95
  messages success: "All good!", error: ->(e) { "Bad news: #{e.message}" }
96
96
  ```
97
97
 
98
- ## `error_for` and `rescues`
98
+ ## `error_from` and `rescues`
99
99
 
100
100
  While `.messages` sets the _default_ error/success messages and is more commonly used, there are times when you want specific error messages for specific failure cases.
101
101
 
102
- `error_for` and `rescues` both register a matcher (exception class, exception class name (string), or callable) and a message to use if the matcher succeeds. They act exactly the same, except if a matcher registered with `rescues` succeeds, the exception _will not_ trigger the configured exception handlers (global or specific to this class).
102
+ `error_from` and `rescues` both register a matcher (exception class, exception class name (string), or callable) and a message to use if the matcher succeeds. They act exactly the same, except if a matcher registered with `rescues` succeeds, the exception _will not_ trigger the configured exception handlers (global or specific to this class).
103
103
 
104
104
  ```ruby
105
105
  messages error: "bad"
@@ -108,8 +108,8 @@ messages error: "bad"
108
108
  rescues ActiveRecord::InvalidRecord => "Invalid params provided"
109
109
 
110
110
  # These WILL trigger error handler (second demonstrates callable matcher AND message)
111
- error_for ArgumentError, ->(e) { "Argument error: #{e.message}" }
112
- error_for -> { name == "bad" }, -> { "was given bad name: #{name}" }
111
+ error_from ArgumentError, ->(e) { "Argument error: #{e.message}" }
112
+ error_from -> { name == "bad" }, -> { "was given bad name: #{name}" }
113
113
  ```
114
114
 
115
115
  ## Callbacks
@@ -153,7 +153,7 @@ class Foo
153
153
  end
154
154
  ```
155
155
 
156
- Note that by default the `on_exception` block will be applied to _any_ `StandardError` that is raised, but you can specify a matcher using the same logic as for [`error_for` and `rescues`](#error-for-and-rescues):
156
+ Note that by default the `on_exception` block will be applied to _any_ `StandardError` that is raised, but you can specify a matcher using the same logic as for [`error_from` and `rescues`](#error-for-and-rescues):
157
157
 
158
158
  ```ruby
159
159
  class Foo
@@ -36,6 +36,22 @@ For example, if you're using Honeybadger this could look something like:
36
36
  end
37
37
  ```
38
38
 
39
+ **Note:** The `action:` and `context:` keyword arguments are *optional*—your proc can accept any combination of `e`, `action:`, and `context:`. Only the keyword arguments you explicitly declare will be passed to your handler. All of the following are valid:
40
+
41
+ ```ruby
42
+ # Only exception object
43
+ c.on_exception = proc { |e| ... }
44
+
45
+ # Exception and action
46
+ c.on_exception = proc { |e, action:| ... }
47
+
48
+ # Exception and context
49
+ c.on_exception = proc { |e, context:| ... }
50
+
51
+ # Exception, action, and context
52
+ c.on_exception = proc { |e, action:, context:| ... }
53
+ ```
54
+
39
55
  A couple notes:
40
56
 
41
57
  * `context` will contain the arguments passed to the `action`, _but_ any marked as sensitive (e.g. `expects :foo, sensitive: true`) will be filtered out in the logs.
@@ -155,4 +155,4 @@ after hook
155
155
  A number of custom callback are available for you as well, if you want to take specific actions when a given Axn succeeds or fails. See the [Class Interface docs](/reference/class#callbacks) for details.
156
156
 
157
157
  ## Strategies
158
- A number of [Strategies](/strategies), which are <abbr title="Don't Repeat Yourself">DRY</abbr>ed bits of commonly-used configuration, are available for your use as well.
158
+ A number of [Strategies](/strategies/index), which are <abbr title="Don't Repeat Yourself">DRY</abbr>ed bits of commonly-used configuration, are available for your use as well.
@@ -2,7 +2,6 @@
2
2
 
3
3
  module Action
4
4
  class Configuration
5
- include Action::Logging
6
5
  attr_accessor :top_level_around_hook
7
6
  attr_writer :logger, :env, :on_exception, :additional_includes, :default_log_level, :default_autolog_level
8
7
 
@@ -12,11 +11,21 @@ module Action
12
11
  def additional_includes = @additional_includes ||= []
13
12
 
14
13
  def on_exception(e, action:, context: {})
15
- if @on_exception
16
- # TODO: only pass action: or context: if requested (and update documentation)
17
- @on_exception.call(e, action:, context:)
14
+ msg = "Handled exception (#{e.class.name}): #{e.message}"
15
+ msg = ("#" * 10) + " #{msg} " + ("#" * 10) unless Action.config.env.production?
16
+ action.log(msg)
17
+
18
+ return unless @on_exception
19
+
20
+ # Only pass the kwargs that the given block expects
21
+ kwargs = @on_exception.parameters.select { |type, _name| %i[key keyreq].include?(type) }.map(&:last)
22
+ kwarg_hash = {}
23
+ kwarg_hash[:action] = action if kwargs.include?(:action)
24
+ kwarg_hash[:context] = context if kwargs.include?(:context)
25
+ if kwarg_hash.any?
26
+ @on_exception.call(e, **kwarg_hash)
18
27
  else
19
- log("[#{action.class.name.presence || "Anonymous Action"}] Exception raised: #{e.class.name} - #{e.message}")
28
+ @on_exception.call(e)
20
29
  end
21
30
  end
22
31
 
@@ -75,8 +75,7 @@ module Action
75
75
  action.instance_exec(&msg)
76
76
  end
77
77
  rescue StandardError => e
78
- action.warn("Ignoring #{e.class.name} while determining message callable: #{e.message}")
79
- nil
78
+ Axn::Util.piping_error("determining message callable", action:, exception: e)
80
79
  end
81
80
  end
82
81
 
@@ -27,8 +27,7 @@ module Action
27
27
  action.instance_exec(exception, &@handler)
28
28
  true
29
29
  rescue StandardError => e
30
- action.warn("Ignoring #{e.class.name} when evaluating handler: #{e.message}")
31
- nil
30
+ Axn::Util.piping_error("executing handler", action:, exception: e)
32
31
  end
33
32
  end
34
33
 
@@ -54,8 +53,7 @@ module Action
54
53
  false
55
54
  end
56
55
  rescue StandardError => e
57
- action.warn("Ignoring #{e.class.name} while determining matcher: #{e.message}")
58
- false
56
+ Axn::Util.piping_error("determining if handler applies to exception", action:, exception: e)
59
57
  end
60
58
 
61
59
  private attr_reader :matcher
@@ -3,7 +3,7 @@
3
3
  require_relative "event_handlers"
4
4
 
5
5
  module Action
6
- module SwallowExceptions
6
+ module HandleExceptions
7
7
  def self.included(base)
8
8
  base.class_eval do
9
9
  class_attribute :_success_msg, :_error_msg
@@ -56,7 +56,7 @@ module Action
56
56
  rescue StandardError => e
57
57
  # No action needed -- downstream #on_exception implementation should ideally log any internal failures, but
58
58
  # we don't want exception *handling* failures to cascade and overwrite the original exception.
59
- warn("Ignoring #{e.class.name} in on_exception hook: #{e.message}")
59
+ Axn::Util.piping_error("executing on_exception hooks", action: self, exception: e)
60
60
  end
61
61
 
62
62
  class << base
@@ -16,20 +16,20 @@ module Action
16
16
  module ClassMethods
17
17
  def default_log_level = Action.config.default_log_level
18
18
 
19
- def log(message, level: default_log_level)
19
+ def log(message, level: default_log_level, before: nil, after: nil)
20
20
  msg = [_log_prefix, message].compact_blank.join(" ")
21
+ msg = [before, msg, after].compact_blank.join if before || after
21
22
 
22
23
  Action.config.logger.send(level, msg)
23
24
  end
24
25
 
25
26
  LEVELS.each do |level|
26
- define_method(level) do |message|
27
- log(message, level:)
27
+ define_method(level) do |message, before: nil, after: nil|
28
+ log(message, level:, before:, after:)
28
29
  end
29
30
  end
30
31
 
31
- # TODO: this is ugly, we should be able to override in the config class...
32
- def _log_prefix = name == "Action::Configuration" ? nil : "[#{name || "Anonymous Class"}]"
32
+ def _log_prefix = "[#{name.presence || "Anonymous Class"}]"
33
33
  end
34
34
  end
35
35
  end
@@ -24,8 +24,9 @@ module Action
24
24
  self.class.default_autolog_level,
25
25
  [
26
26
  "About to execute",
27
- context_for_logging(:inbound).presence&.inspect,
27
+ _log_context(:inbound),
28
28
  ].compact.join(" with: "),
29
+ before: Action.config.env.production? ? nil : "\n------\n",
29
30
  )
30
31
  end
31
32
 
@@ -36,21 +37,38 @@ module Action
36
37
  self.class.default_autolog_level,
37
38
  [
38
39
  "Execution completed (with outcome: #{outcome}) in #{elapsed_mils} milliseconds",
39
- _log_after_data_peak,
40
+ _log_context(:outbound),
40
41
  ].compact.join(". Set: "),
42
+ after: Action.config.env.production? ? nil : "\n------\n",
41
43
  )
42
44
  end
43
45
 
44
- def _log_after_data_peak
45
- return unless (data = context_for_logging(:outbound)).present?
46
+ def _log_context(direction)
47
+ data = context_for_logging(direction)
48
+ return unless data.present?
46
49
 
47
- max_length = 100
48
- suffix = "...<truncated>...}"
50
+ max_length = 150
51
+ suffix = "…<truncated>…"
49
52
 
50
- data.inspect.tap do |str|
53
+ _log_object(data).tap do |str|
51
54
  return str[0, max_length - suffix.length] + suffix if str.length > max_length
52
55
  end
53
56
  end
57
+
58
+ def _log_object(data)
59
+ case data
60
+ when Hash
61
+ # NOTE: slightly more manual in order to avoid quotes around ActiveRecord objects' <Class#id> formatting
62
+ "{#{data.map { |k, v| "#{k}: #{_log_object(v)}" }.join(", ")}}"
63
+ when Array
64
+ data.map { |v| _log_object(v) }
65
+ else
66
+ return data.to_unsafe_h if defined?(ActionController::Parameters) && data.is_a?(ActionController::Parameters)
67
+ return "<#{data.class.name}##{data.to_param.presence || "unpersisted"}>" if defined?(ActiveRecord::Base) && data.is_a?(ActiveRecord::Base)
68
+
69
+ data.inspect
70
+ end
71
+ end
54
72
  end
55
73
 
56
74
  module InstanceMethods
@@ -26,8 +26,7 @@ module Action
26
26
  msg = id.blank? ? "not found (given a blank ID)" : "not found for class #{klass.name} and ID #{id}"
27
27
  record.errors.add(attribute, msg)
28
28
  rescue StandardError => e
29
- warn("Model validation on field '#{attribute}' raised #{e.class.name}: #{e.message}")
30
-
29
+ Axn::Util.piping_error("applying model validation on field '#{attribute}'", exception: e)
31
30
  record.errors.add(attribute, "error raised while trying to find a valid #{klass.name}")
32
31
  end
33
32
  end
@@ -9,7 +9,7 @@ module Action
9
9
  msg = begin
10
10
  options[:with].call(value)
11
11
  rescue StandardError => e
12
- Action.config.logger.warn("Custom validation on field '#{attribute}' raised #{e.class.name}: #{e.message}")
12
+ Axn::Util.piping_error("applying custom validation on field '#{attribute}'", exception: e)
13
13
 
14
14
  "failed validation: #{e.message}"
15
15
  end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "rspec"
4
-
5
3
  module Axn
6
4
  module Testing
7
5
  module SpecHelpers
data/lib/axn/util.rb ADDED
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Axn
4
+ module Util
5
+ def self.piping_error(desc, exception:, action: nil)
6
+ # Extract just filename/line number from backtrace
7
+ src = exception.backtrace.first.split.first.split("/").last.split(":")[0, 2].join(":")
8
+
9
+ message = if Action.config.env.production?
10
+ "Ignoring exception raised while #{desc}: #{exception.class.name} - #{exception.message} (from #{src})"
11
+ else
12
+ msg = "!! IGNORING EXCEPTION RAISED WHILE #{desc.upcase} !!\n\n" \
13
+ "\t* Exception: #{exception.class.name}\n" \
14
+ "\t* Message: #{exception.message}\n" \
15
+ "\t* From: #{src}"
16
+ "#{"⌵" * 30}\n\n#{msg}\n\n#{"^" * 30}"
17
+ end
18
+
19
+ (action || Action.config.logger).send(:warn, message)
20
+
21
+ nil
22
+ end
23
+ end
24
+ end
data/lib/axn/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Axn
4
- VERSION = "0.1.0-alpha.2.5.2"
4
+ VERSION = "0.1.0-alpha.2.5.3.1"
5
5
  end
data/lib/axn.rb CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  module Axn; end
4
4
  require_relative "axn/version"
5
+ require_relative "axn/util"
5
6
 
6
7
  require "interactor"
7
8
  require "active_support"
@@ -16,7 +17,7 @@ require_relative "action/core/configuration"
16
17
  require_relative "action/core/top_level_around_hook"
17
18
  require_relative "action/core/contract"
18
19
  require_relative "action/core/contract_for_subfields"
19
- require_relative "action/core/swallow_exceptions"
20
+ require_relative "action/core/handle_exceptions"
20
21
  require_relative "action/core/hoist_errors"
21
22
  require_relative "action/core/use_strategy"
22
23
 
@@ -44,7 +45,7 @@ module Action
44
45
  # can include those hook executions in any traces set from this hook.
45
46
  include TopLevelAroundHook
46
47
 
47
- include SwallowExceptions
48
+ include HandleExceptions
48
49
  include Contract
49
50
  include ContractForSubfields
50
51
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: axn
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0.pre.alpha.2.5.2
4
+ version: 0.1.0.pre.alpha.2.5.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kali Donovan
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-07-07 00:00:00.000000000 Z
11
+ date: 2025-07-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activemodel
@@ -96,9 +96,9 @@ files:
96
96
  - lib/action/core/contract_for_subfields.rb
97
97
  - lib/action/core/event_handlers.rb
98
98
  - lib/action/core/exceptions.rb
99
+ - lib/action/core/handle_exceptions.rb
99
100
  - lib/action/core/hoist_errors.rb
100
101
  - lib/action/core/logging.rb
101
- - lib/action/core/swallow_exceptions.rb
102
102
  - lib/action/core/top_level_around_hook.rb
103
103
  - lib/action/core/use_strategy.rb
104
104
  - lib/action/core/validation/fields.rb
@@ -113,6 +113,7 @@ files:
113
113
  - lib/axn.rb
114
114
  - lib/axn/factory.rb
115
115
  - lib/axn/testing/spec_helpers.rb
116
+ - lib/axn/util.rb
116
117
  - lib/axn/version.rb
117
118
  - package.json
118
119
  - yarn.lock