Class: Axn::Result
- Inherits:
-
Core::ContextFacade
- Object
- Core::ContextFacade
- Axn::Result
- Defined in:
- lib/axn/result.rb
Overview
Outbound / External ContextFacade
Constant Summary collapse
- OUTCOMES =
Outcome constants for action execution results
[ OUTCOME_SUCCESS = "success", OUTCOME_FAILURE = "failure", OUTCOME_EXCEPTION = "exception", ].freeze
Class Method Summary collapse
Instance Method Summary collapse
-
#__action__ ⇒ Object
Internal accessor for the underlying action instance (used by introspection and tests).
-
#deconstruct_keys(keys) ⇒ Object
Enable pattern matching support for Ruby 3+.
-
#error ⇒ Object
Memoized once the context is finalized, so resolution (which can invoke user-supplied message blocks) runs a single time across the lifecycle (logging) and every caller read.
-
#initialize ⇒ Result
constructor
A new instance of Result.
- #message ⇒ Object
-
#outcome ⇒ Object
Deliberately NOT memoized (unlike #error/#success): outcome reflects classification state that can finalize at different points during dispatch (records #2/#3 below), so a value read early — e.g.
- #success ⇒ Object
Constructor Details
#initialize ⇒ Result
Returns a new instance of Result.
9 10 11 12 |
# File 'lib/axn/result.rb', line 9 def initialize(...) super _define_boolean_predicate_readers end |
Dynamic Method Handling
This class handles dynamic methods through the method_missing method
#method_missing(method_name) ⇒ Object (private)
rubocop:disable Style/MissingRespondToMissing (because we're not actually responding to anything additional)
241 242 243 244 245 246 247 248 249 250 251 252 253 254 |
# File 'lib/axn/result.rb', line 241 def method_missing(method_name, ...) # rubocop:disable Style/MissingRespondToMissing (because we're not actually responding to anything additional) if @context.__combined_data.key?(method_name.to_sym) msg = <<~MSG Method ##{method_name} is not available on Action::Result! #{action_name} may be missing a line like: exposes :#{method_name} MSG raise Axn::ContractViolation::MethodNotAllowed, msg end super end |
Class Method Details
.error(msg = nil, **exposures, &block) ⇒ Object
26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
# File 'lib/axn/result.rb', line 26 def error(msg = nil, **exposures, &block) exposes = exposures.keys.to_h { |key| [key, { optional: true }] } Axn::Factory.build(exposes:, error: msg, auto_log: false) do exposures.each do |key, value| expose(key, value) end if block_given? begin block.call rescue StandardError => e # Set the exception directly without triggering on_exception handlers @__context.__record_exception(e) end else fail! msg, standalone: true end end.call end |
.ok(msg = nil, **exposures) ⇒ Object
16 17 18 19 20 21 22 23 24 |
# File 'lib/axn/result.rb', line 16 def ok(msg = nil, **exposures) exposes = exposures.keys.to_h { |key| [key, { optional: true }] } Axn::Factory.build(exposes:, success: msg, auto_log: false) do exposures.each do |key, value| expose(key, value) end end.call end |
Instance Method Details
#__action__ ⇒ Object
Internal accessor for the underlying action instance (used by introspection and tests). It is a reserved public field — see reserved_attribute_names_spec — so it stays public.
108 |
# File 'lib/axn/result.rb', line 108 def __action__ = @action |
#deconstruct_keys(keys) ⇒ Object
Enable pattern matching support for Ruby 3+
111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 |
# File 'lib/axn/result.rb', line 111 def deconstruct_keys(keys) attrs = { ok: ok?, success:, error:, message:, outcome: outcome.to_sym, finalized: finalized?, } # Add all exposed data attrs.merge!(@context.exposed_data) # Return filtered attributes if keys specified keys ? attrs.slice(*keys) : attrs end |
#error ⇒ Object
Memoized once the context is finalized, so resolution (which can invoke user-supplied message
blocks) runs a single time across the lifecycle (logging) and every caller read. A Result is the
SAME object during and after the run, so we must NOT cache a pre-finalization read — e.g. a hook
touching result.success/#message mid-run, where ok? is still true but a later done!/expose
would change the answer. Pre-finalization reads resolve live; only a finalized result is frozen in.
55 56 57 58 59 60 61 |
# File 'lib/axn/result.rb', line 55 def error return if ok? # (!ok? implies finalized — a failure sets the finalized flag — but be explicit) return _resolve_error unless finalized? @__resolved_error = _resolve_error unless defined?(@__resolved_error) @__resolved_error end |
#message ⇒ Object
71 |
# File 'lib/axn/result.rb', line 71 def = exception ? error : success |
#outcome ⇒ Object
Deliberately NOT memoized (unlike #error/#success): outcome reflects classification state that can finalize at different points during dispatch (records #2/#3 below), so a value read early — e.g. by an ancestor's on_error before this level's context flag is set — must not be frozen in. The recompute is cheap: it short-circuits on the common paths and only allocates a StringInquirer.
84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 |
# File 'lib/axn/result.rb', line 84 def outcome label = if exception.is_a?(Axn::Failure) OUTCOME_FAILURE elsif exception # Three records of "this settled as a failure", in priority order: # 1. context flag — durable; survives after the per-execution set is cleared. # 2. live classification set — set as soon as ANY action (this one or a nested one, # sticky) classifies the exception. Covers the window where an ancestor's `on_error` # reads outcome *before* the executor sets the context flag on this level. # 3. `_fails_on?` — defensive recompute. failure = @context.__classified_as_failure? || Internal::ExceptionClassification.failure?(exception) || action.class._fails_on?(exception) || Axn::ValidationError.user_facing?(exception) failure ? OUTCOME_FAILURE : OUTCOME_EXCEPTION else OUTCOME_SUCCESS end ActiveSupport::StringInquirer.new(label) end |
#success ⇒ Object
63 64 65 66 67 68 69 |
# File 'lib/axn/result.rb', line 63 def success return unless ok? return _resolve_success unless finalized? @__resolved_success = _resolve_success unless defined?(@__resolved_success) @__resolved_success end |