axn 0.1.0.pre.alpha.2.5.3.1 → 0.1.0.pre.alpha.2.6.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.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +4 -1
  3. data/CHANGELOG.md +25 -1
  4. data/README.md +2 -11
  5. data/docs/reference/action-result.md +2 -0
  6. data/docs/reference/class.md +12 -4
  7. data/docs/reference/configuration.md +53 -20
  8. data/docs/reference/instance.md +2 -2
  9. data/docs/strategies/index.md +1 -1
  10. data/docs/usage/setup.md +1 -1
  11. data/docs/usage/writing.md +9 -9
  12. data/lib/action/attachable/steps.rb +16 -1
  13. data/lib/action/attachable.rb +3 -3
  14. data/lib/action/{core/configuration.rb → configuration.rb} +3 -4
  15. data/lib/action/context.rb +38 -0
  16. data/lib/action/core/automatic_logging.rb +93 -0
  17. data/lib/action/core/context/facade.rb +69 -0
  18. data/lib/action/core/context/facade_inspector.rb +63 -0
  19. data/lib/action/core/context/internal.rb +32 -0
  20. data/lib/action/core/contract.rb +167 -211
  21. data/lib/action/core/contract_for_subfields.rb +84 -82
  22. data/lib/action/core/contract_validation.rb +62 -0
  23. data/lib/action/core/flow/callbacks.rb +54 -0
  24. data/lib/action/core/flow/exception_execution.rb +79 -0
  25. data/lib/action/core/flow/messages.rb +61 -0
  26. data/lib/action/core/flow.rb +19 -0
  27. data/lib/action/core/hoist_errors.rb +42 -40
  28. data/lib/action/core/hooks.rb +123 -0
  29. data/lib/action/core/logging.rb +22 -20
  30. data/lib/action/core/timing.rb +40 -0
  31. data/lib/action/core/tracing.rb +17 -0
  32. data/lib/action/core/use_strategy.rb +19 -17
  33. data/lib/action/core/validation/fields.rb +2 -0
  34. data/lib/action/core.rb +100 -0
  35. data/lib/action/enqueueable/via_sidekiq.rb +2 -2
  36. data/lib/action/enqueueable.rb +1 -1
  37. data/lib/action/{core/exceptions.rb → exceptions.rb} +1 -19
  38. data/lib/action/result.rb +95 -0
  39. data/lib/axn/factory.rb +27 -9
  40. data/lib/axn/version.rb +1 -1
  41. data/lib/axn.rb +10 -47
  42. metadata +19 -21
  43. data/lib/action/core/context_facade.rb +0 -209
  44. data/lib/action/core/handle_exceptions.rb +0 -163
  45. data/lib/action/core/top_level_around_hook.rb +0 -108
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "action/context"
4
+
5
+ require "action/strategies"
6
+ require "action/core/hooks"
7
+ require "action/core/logging"
8
+ require "action/core/hoist_errors"
9
+ require "action/core/flow"
10
+ require "action/core/automatic_logging"
11
+ require "action/core/use_strategy"
12
+ require "action/core/tracing"
13
+
14
+ # CONSIDER: make class names match file paths?
15
+ require "action/core/validation/validators/model_validator"
16
+ require "action/core/validation/validators/type_validator"
17
+ require "action/core/validation/validators/validate_validator"
18
+
19
+ require "action/core/contract_validation"
20
+ require "action/core/contract"
21
+ require "action/core/contract_for_subfields"
22
+ require "action/core/timing"
23
+
24
+ module Action
25
+ module Core
26
+ def self.included(base)
27
+ base.class_eval do
28
+ extend ClassMethods
29
+ include Core::Hooks
30
+ include Core::Logging
31
+ include Core::AutomaticLogging
32
+ include Core::Tracing
33
+ include Core::Timing
34
+
35
+ include Core::Flow
36
+
37
+ include Core::ContractValidation
38
+ include Core::Contract
39
+ include Core::ContractForSubfields
40
+
41
+ include Core::HoistErrors
42
+ include Core::UseStrategy
43
+ end
44
+ end
45
+
46
+ module ClassMethods
47
+ def call(**)
48
+ new(**).tap(&:_run).result
49
+ end
50
+
51
+ def call!(**)
52
+ result = call(**)
53
+ return result if result.ok?
54
+
55
+ raise result.exception || Action::Failure.new(result.error)
56
+ end
57
+ end
58
+
59
+ def initialize(**)
60
+ @__context = Action::Context.new(**)
61
+ end
62
+
63
+ # Main entry point for action execution
64
+ def _run
65
+ _with_tracing do
66
+ _with_logging do
67
+ _with_timing do
68
+ _with_exception_handling do # Exceptions stop here; outer wrappers access result status (and must not introduce another exception layer)
69
+ _with_contract do # Library internals -- any failures (e.g. contract violations) *should* fail the Action::Result
70
+ _with_hooks do # User hooks -- any failures here *should* fail the Action::Result
71
+ call
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
78
+ ensure
79
+ _emit_metrics
80
+ end
81
+
82
+ # User-defined action logic - override this method in your action classes
83
+ def call; end
84
+
85
+ delegate :fail!, to: :@__context
86
+
87
+ private
88
+
89
+ def _emit_metrics
90
+ return unless Action.config.emit_metrics
91
+
92
+ Action.config.emit_metrics.call(
93
+ self.class.name || "AnonymousClass",
94
+ result,
95
+ )
96
+ rescue StandardError => e
97
+ Axn::Util.piping_error("running metrics hook", action: self, exception: e)
98
+ end
99
+ end
100
+ end
@@ -18,9 +18,9 @@ module Action
18
18
  bang = args.size > 1 ? args.last : false
19
19
 
20
20
  if bang
21
- self.class.call!(context)
21
+ self.class.call!(**context)
22
22
  else
23
- self.class.call(context)
23
+ self.class.call(**context)
24
24
  end
25
25
  end
26
26
 
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "enqueueable/via_sidekiq"
3
+ require "action/enqueueable/via_sidekiq"
4
4
 
5
5
  module Action
6
6
  module Enqueueable
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Action
4
- # Raised internally when fail! is called -- triggers failure + rollback handling
4
+ # Raised internally when fail! is called
5
5
  class Failure < StandardError
6
6
  DEFAULT_MESSAGE = "Execution was halted"
7
7
 
@@ -17,24 +17,6 @@ module Action
17
17
  def inspect = "#<#{self.class.name} '#{message}'>"
18
18
  end
19
19
 
20
- class StepsRequiredForInheritanceSupportError < StandardError
21
- def message
22
- <<~MSG
23
- ** Inheritance support requires the following steps: **
24
-
25
- Add this to your Gemfile:
26
- gem "interactor", github: "kaspermeyer/interactor", branch: "fix-hook-inheritance"
27
-
28
- Explanation:
29
- Unfortunately the upstream interactor gem does not support inheritance of hooks, which is required for this feature.
30
- This branch is a temporary fork that adds support for inheritance of hooks, but published gems cannot specify a branch dependency.
31
- In the future we may inline the upstream Interactor gem entirely and remove this necessity, but while we're in alpha we're continuing
32
- to use the upstream gem for stability (and there has been recent activity on the project, so they *may* be adding additional functionality
33
- soon).
34
- MSG
35
- end
36
- end
37
-
38
20
  class ContractViolation < StandardError
39
21
  class ReservedAttributeError < ContractViolation
40
22
  def initialize(name)
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "action/core/context/facade"
4
+ require "action/core/context/facade_inspector"
5
+
6
+ module Action
7
+ # Outbound / External ContextFacade
8
+ class Result < ContextFacade
9
+ # For ease of mocking return results in tests
10
+ class << self
11
+ def ok(msg = nil, **exposures)
12
+ exposes = exposures.keys.to_h { |key| [key, { allow_blank: true }] }
13
+
14
+ Axn::Factory.build(exposes:, messages: { success: msg }) do
15
+ exposures.each do |key, value|
16
+ expose(key, value)
17
+ end
18
+ end.call
19
+ end
20
+
21
+ def error(msg = nil, **exposures, &block)
22
+ exposes = exposures.keys.to_h { |key| [key, { allow_blank: true }] }
23
+ rescues = [-> { true }, msg]
24
+
25
+ Axn::Factory.build(exposes:, rescues:) do
26
+ exposures.each do |key, value|
27
+ expose(key, value)
28
+ end
29
+ block.call if block_given?
30
+ fail!
31
+ end.call
32
+ end
33
+ end
34
+
35
+ # Poke some holes for necessary internal control methods
36
+ delegate :each_pair, to: :context
37
+
38
+ # External interface
39
+ delegate :ok?, :exception, to: :context
40
+
41
+ def error
42
+ return if ok?
43
+
44
+ [@context.error_prefix, determine_error_message].compact.join(" ").squeeze(" ")
45
+ end
46
+
47
+ def success
48
+ return unless ok?
49
+
50
+ stringified(action._success_msg).presence || "Action completed successfully"
51
+ end
52
+
53
+ def ok = success
54
+
55
+ def message = error || success
56
+
57
+ # Outcome constants for action execution results
58
+ OUTCOMES = [
59
+ OUTCOME_SUCCESS = :success,
60
+ OUTCOME_FAILURE = :failure,
61
+ OUTCOME_EXCEPTION = :exception,
62
+ ].freeze
63
+
64
+ def outcome
65
+ return OUTCOME_EXCEPTION if exception
66
+ return OUTCOME_FAILURE if @context.failed?
67
+
68
+ OUTCOME_SUCCESS
69
+ end
70
+
71
+ # Elapsed time in milliseconds
72
+ def elapsed_time
73
+ @context.elapsed_time
74
+ end
75
+
76
+ private
77
+
78
+ def context_data_source = @context.exposed_data
79
+
80
+ def method_missing(method_name, ...) # rubocop:disable Style/MissingRespondToMissing (because we're not actually responding to anything additional)
81
+ if @context.__combined_data.key?(method_name.to_sym)
82
+ msg = <<~MSG
83
+ Method ##{method_name} is not available on Action::Result!
84
+
85
+ #{action_name} may be missing a line like:
86
+ exposes :#{method_name}
87
+ MSG
88
+
89
+ raise Action::ContractViolation::MethodNotAllowed, msg
90
+ end
91
+
92
+ super
93
+ end
94
+ end
95
+ end
data/lib/axn/factory.rb CHANGED
@@ -22,8 +22,15 @@ module Axn
22
22
  after: nil,
23
23
  around: nil,
24
24
 
25
- # Allow dynamically assigning rollback method
26
- rollback: nil,
25
+ # Callbacks
26
+ on_success: nil,
27
+ on_failure: nil,
28
+ on_error: nil,
29
+ on_exception: nil,
30
+
31
+ # Strategies
32
+ use: [],
33
+
27
34
  &block
28
35
  )
29
36
  args = block.parameters.each_with_object(_hash_with_default_array) { |(type, field), hash| hash[type] << field }
@@ -84,13 +91,24 @@ module Axn
84
91
  axn.after(after) if after.present?
85
92
  axn.around(around) if around.present?
86
93
 
87
- # Rollback
88
- if rollback.present?
89
- raise ArgumentError, "[Axn::Factory] Rollback must be a callable" unless rollback.respond_to?(:call) && rollback.respond_to?(:arity)
90
- raise ArgumentError, "[Axn::Factory] Rollback must be a callable with no arguments" unless rollback.arity.zero?
91
-
92
- axn.define_method(:rollback) do
93
- instance_exec(&rollback)
94
+ # Callbacks
95
+ axn.on_success(&on_success) if on_success.present?
96
+ axn.on_failure(&on_failure) if on_failure.present?
97
+ axn.on_error(&on_error) if on_error.present?
98
+ axn.on_exception(&on_exception) if on_exception.present?
99
+
100
+ # Strategies
101
+ Array(use).each do |strategy|
102
+ if strategy.is_a?(Array)
103
+ strategy_name, *config_args = strategy
104
+ if config_args.last.is_a?(Hash)
105
+ *other_args, config = config_args
106
+ axn.use(strategy_name, *other_args, **config)
107
+ else
108
+ axn.use(strategy_name, *config_args)
109
+ end
110
+ else
111
+ axn.use(strategy)
94
112
  end
95
113
  end
96
114
 
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.3.1"
4
+ VERSION = "0.1.0-alpha.2.6.1"
5
5
  end
data/lib/axn.rb CHANGED
@@ -1,31 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Axn; end
4
- require_relative "axn/version"
5
- require_relative "axn/util"
6
-
7
- require "interactor"
8
3
  require "active_support"
9
4
 
10
- require_relative "action/core/validation/validators/model_validator"
11
- require_relative "action/core/validation/validators/type_validator"
12
- require_relative "action/core/validation/validators/validate_validator"
5
+ module Axn; end
6
+ require "axn/version"
7
+ require "axn/util"
8
+ require "axn/factory"
13
9
 
14
- require_relative "action/core/exceptions"
15
- require_relative "action/core/logging"
16
- require_relative "action/core/configuration"
17
- require_relative "action/core/top_level_around_hook"
18
- require_relative "action/core/contract"
19
- require_relative "action/core/contract_for_subfields"
20
- require_relative "action/core/handle_exceptions"
21
- require_relative "action/core/hoist_errors"
22
- require_relative "action/core/use_strategy"
10
+ require "action/configuration"
11
+ require "action/exceptions"
23
12
 
24
- require_relative "axn/factory"
13
+ require "action/core"
25
14
 
26
- require_relative "action/attachable"
27
- require_relative "action/enqueueable"
28
- require_relative "action/strategies"
15
+ require "action/attachable"
16
+ require "action/enqueueable"
29
17
 
30
18
  def Axn(callable, **) # rubocop:disable Naming/MethodName
31
19
  return callable if callable.is_a?(Class) && callable < Action
@@ -36,22 +24,7 @@ end
36
24
  module Action
37
25
  def self.included(base)
38
26
  base.class_eval do
39
- include Interactor
40
-
41
- # Include first so other modules can assume `log` is available
42
- include Logging
43
-
44
- # NOTE: include before any others that set hooks (like contract validation), so we
45
- # can include those hook executions in any traces set from this hook.
46
- include TopLevelAroundHook
47
-
48
- include HandleExceptions
49
- include Contract
50
- include ContractForSubfields
51
-
52
- include HoistErrors
53
-
54
- include UseStrategy
27
+ include Core
55
28
 
56
29
  # --- Extensions ---
57
30
  include Attachable
@@ -59,16 +32,6 @@ module Action
59
32
 
60
33
  # Allow additional automatic includes to be configured
61
34
  Array(Action.config.additional_includes).each { |mod| include mod }
62
-
63
- # ----
64
-
65
- # ALPHA: Everything below here is to support inheritance
66
-
67
- base.define_singleton_method(:inherited) do |base_klass|
68
- return super(base_klass) if Interactor::Hooks::ClassMethods.private_method_defined?(:ancestor_hooks)
69
-
70
- raise StepsRequiredForInheritanceSupportError
71
- end
72
35
  end
73
36
  end
74
37
  end
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.3.1
4
+ version: 0.1.0.pre.alpha.2.6.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-30 00:00:00.000000000 Z
11
+ date: 2025-08-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activemodel
@@ -38,20 +38,6 @@ dependencies:
38
38
  - - ">"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '7.0'
41
- - !ruby/object:Gem::Dependency
42
- name: interactor
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - '='
46
- - !ruby/object:Gem::Version
47
- version: 3.1.2
48
- type: :runtime
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - '='
53
- - !ruby/object:Gem::Version
54
- version: 3.1.2
55
41
  description: Pattern for writing callable service objects with contract validation
56
42
  and error swallowing
57
43
  email:
@@ -90,16 +76,26 @@ files:
90
76
  - lib/action/attachable/base.rb
91
77
  - lib/action/attachable/steps.rb
92
78
  - lib/action/attachable/subactions.rb
93
- - lib/action/core/configuration.rb
94
- - lib/action/core/context_facade.rb
79
+ - lib/action/configuration.rb
80
+ - lib/action/context.rb
81
+ - lib/action/core.rb
82
+ - lib/action/core/automatic_logging.rb
83
+ - lib/action/core/context/facade.rb
84
+ - lib/action/core/context/facade_inspector.rb
85
+ - lib/action/core/context/internal.rb
95
86
  - lib/action/core/contract.rb
96
87
  - lib/action/core/contract_for_subfields.rb
88
+ - lib/action/core/contract_validation.rb
97
89
  - lib/action/core/event_handlers.rb
98
- - lib/action/core/exceptions.rb
99
- - lib/action/core/handle_exceptions.rb
90
+ - lib/action/core/flow.rb
91
+ - lib/action/core/flow/callbacks.rb
92
+ - lib/action/core/flow/exception_execution.rb
93
+ - lib/action/core/flow/messages.rb
100
94
  - lib/action/core/hoist_errors.rb
95
+ - lib/action/core/hooks.rb
101
96
  - lib/action/core/logging.rb
102
- - lib/action/core/top_level_around_hook.rb
97
+ - lib/action/core/timing.rb
98
+ - lib/action/core/tracing.rb
103
99
  - lib/action/core/use_strategy.rb
104
100
  - lib/action/core/validation/fields.rb
105
101
  - lib/action/core/validation/subfields.rb
@@ -108,6 +104,8 @@ files:
108
104
  - lib/action/core/validation/validators/validate_validator.rb
109
105
  - lib/action/enqueueable.rb
110
106
  - lib/action/enqueueable/via_sidekiq.rb
107
+ - lib/action/exceptions.rb
108
+ - lib/action/result.rb
111
109
  - lib/action/strategies.rb
112
110
  - lib/action/strategies/transaction.rb
113
111
  - lib/axn.rb
@@ -1,209 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "active_support/parameter_filter"
4
-
5
- module Action
6
- class ContextFacade
7
- def initialize(action:, context:, declared_fields:, implicitly_allowed_fields: nil)
8
- if self.class.name == "Action::ContextFacade" # rubocop:disable Style/ClassEqualityComparison
9
- raise "Action::ContextFacade is an abstract class and should not be instantiated directly"
10
- end
11
-
12
- @context = context
13
- @action = action
14
- @declared_fields = declared_fields
15
-
16
- (@declared_fields + Array(implicitly_allowed_fields)).each do |field|
17
- singleton_class.define_method(field) { @context.public_send(field) }
18
- end
19
- end
20
-
21
- attr_reader :declared_fields
22
-
23
- def inspect = Inspector.new(facade: self, action:, context:).call
24
-
25
- def fail!(...)
26
- raise Action::ContractViolation::MethodNotAllowed, "Call fail! directly rather than on the context"
27
- end
28
-
29
- private
30
-
31
- attr_reader :action, :context
32
-
33
- def exposure_method_name = raise NotImplementedError
34
-
35
- # Add nice error message for missing methods
36
- def method_missing(method_name, ...) # rubocop:disable Style/MissingRespondToMissing (because we're not actually responding to anything additional)
37
- if context.respond_to?(method_name)
38
- msg = <<~MSG
39
- Method ##{method_name} is not available on #{self.class.name}!
40
-
41
- #{@action.class.name || "The action"} may be missing a line like:
42
- #{exposure_method_name} :#{method_name}
43
- MSG
44
-
45
- raise Action::ContractViolation::MethodNotAllowed, msg
46
- end
47
-
48
- super
49
- end
50
-
51
- def determine_error_message(only_default: false)
52
- return @context.error_from_user if @context.error_from_user.present?
53
-
54
- # We need an exception for interceptors, and also in case the messages.error callable expects an argument
55
- exception = @context.exception || Action::Failure.new
56
-
57
- msg = action._error_msg
58
-
59
- unless only_default
60
- interceptor = action.class._error_interceptor_for(exception:, action:)
61
- msg = interceptor.message if interceptor
62
- end
63
-
64
- stringified(msg, exception:).presence || "Something went wrong"
65
- end
66
-
67
- # Allow for callable OR string messages
68
- def stringified(msg, exception: nil)
69
- return msg.presence unless msg.respond_to?(:call)
70
-
71
- # The error message callable can take the exception as an argument
72
- if exception && msg.arity == 1
73
- action.instance_exec(exception, &msg)
74
- else
75
- action.instance_exec(&msg)
76
- end
77
- rescue StandardError => e
78
- Axn::Util.piping_error("determining message callable", action:, exception: e)
79
- end
80
- end
81
-
82
- # Inbound / Internal ContextFacade
83
- class InternalContext < ContextFacade
84
- # So can be referenced from within e.g. rescues callables
85
- def default_error
86
- [@context.error_prefix, determine_error_message(only_default: true)].compact.join(" ").squeeze(" ")
87
- end
88
-
89
- private
90
-
91
- def exposure_method_name = :expects
92
- end
93
-
94
- # Outbound / External ContextFacade
95
- class Result < ContextFacade
96
- # For ease of mocking return results in tests
97
- class << self
98
- def ok(msg = nil, **exposures)
99
- exposes = exposures.keys.to_h { |key| [key, { allow_blank: true }] }
100
-
101
- Axn::Factory.build(exposes:, messages: { success: msg }) do
102
- exposures.each do |key, value|
103
- expose(key, value)
104
- end
105
- end.call
106
- end
107
-
108
- def error(msg = nil, **exposures, &block)
109
- exposes = exposures.keys.to_h { |key| [key, { allow_blank: true }] }
110
- rescues = [-> { true }, msg]
111
-
112
- Axn::Factory.build(exposes:, rescues:) do
113
- exposures.each do |key, value|
114
- expose(key, value)
115
- end
116
- block.call if block_given?
117
- fail!
118
- end.call
119
- end
120
- end
121
-
122
- # Poke some holes for necessary internal control methods
123
- delegate :called!, :rollback!, :each_pair, to: :context
124
-
125
- # External interface
126
- delegate :success?, :exception, to: :context
127
- def ok? = success?
128
-
129
- def error
130
- return if ok?
131
-
132
- [@context.error_prefix, determine_error_message].compact.join(" ").squeeze(" ")
133
- end
134
-
135
- def success
136
- return unless ok?
137
-
138
- stringified(action._success_msg).presence || "Action completed successfully"
139
- end
140
-
141
- def ok = success
142
-
143
- def message = error || success
144
-
145
- private
146
-
147
- def exposure_method_name = :exposes
148
- end
149
-
150
- class Inspector
151
- def initialize(action:, facade:, context:)
152
- @action = action
153
- @facade = facade
154
- @context = context
155
- end
156
-
157
- def call
158
- str = [status, visible_fields].compact_blank.join(" ")
159
-
160
- "#<#{class_name} #{str}>"
161
- end
162
-
163
- private
164
-
165
- attr_reader :action, :facade, :context
166
-
167
- def status
168
- return unless facade.is_a?(Action::Result)
169
-
170
- return "[OK]" if context.success?
171
- unless context.exception
172
- return context.error_from_user.present? ? "[failed with '#{context.error_from_user}']" : "[failed]"
173
- end
174
-
175
- %([failed with #{context.exception.class.name}: '#{context.exception.message}'])
176
- end
177
-
178
- def visible_fields
179
- declared_fields.map do |field|
180
- value = facade.public_send(field)
181
-
182
- "#{field}: #{format_for_inspect(field, value)}"
183
- end.join(", ")
184
- end
185
-
186
- def class_name = facade.class.name
187
- def declared_fields = facade.send(:declared_fields)
188
-
189
- def format_for_inspect(field, value)
190
- return value.inspect if value.nil?
191
-
192
- # Initially based on https://github.com/rails/rails/blob/800976975253be2912d09a80757ee70a2bb1e984/activerecord/lib/active_record/attribute_methods.rb#L527
193
- inspected_value = if value.is_a?(String) && value.length > 50
194
- "#{value[0, 50]}...".inspect
195
- elsif value.is_a?(Date) || value.is_a?(Time)
196
- %("#{value.to_fs(:inspect)}")
197
- elsif defined?(::ActiveRecord::Relation) && value.instance_of?(::ActiveRecord::Relation)
198
- # Avoid hydrating full AR relation (i.e. avoid loading records just to report an error)
199
- "#{value.name}::ActiveRecord_Relation"
200
- else
201
- value.inspect
202
- end
203
-
204
- inspection_filter.filter_param(field, inspected_value)
205
- end
206
-
207
- def inspection_filter = action.send(:inspection_filter)
208
- end
209
- end