Axn::Result
Every call invocation on an Axn will return an Axn::Result instance, which provides a consistent interface:
| Method | Description |
|---|---|
ok? | true if the call succeeded, false if not. |
error | User-facing error message (string), if not ok? (else nil) |
success | User-facing success message (string), if ok? (else nil) |
message | User-facing message (string), always defined (ok? ? success : error) |
exception | If not ok? because an exception was swallowed, will be set to the swallowed exception (note: rarely used outside development; prefer to let the library automatically handle exception handling for you) |
outcome | The execution outcome as a string inquirer (success?, failure?, exception?) |
elapsed_time | Execution time in milliseconds (Float) |
finalized? | true if the result has completed execution (success, failure, or exception), false if still in progress |
any exposed values | guaranteed to be set if ok?; fields that are also expects-declared are auto-copied and available on fail! and exception paths too (see Re-exposing an expected field) |
NOTE: success and error (and so implicitly message) can be configured per-action via the success and error declarations.
Clarification of exposed values
In addition to the core interface, your Action's Result class will have methods defined to read the values of any attributes that were explicitly exposed. For example, given this action and result:
class Foo
include Axn
exposes :bar, :baz
def call
expose bar: 1, baz: 2
end
end
result = Foo.callresult will have both bar and baz reader methods (which will return 1 and 2, respectively).
Which Axn decided a failure
fail! raises an Axn::Failure, and that exception knows which axn raised it. This matters when a nested call tree bubbles a failure up through call!: the outermost caller holds one exception, and the guard that actually fired may be several actions down.
| Method | Description |
|---|---|
originating_axn_class | The class of the axn whose fail! raised, or nil |
originating_result | That axn's result — carrying anything it had exposed before failing — or nil |
These are defined on Axn::Failure alone, and a failed outcome does not imply one: a fails_on-classified exception keeps its own class, and a user_facing: contract violation settles as an Axn::InboundValidationError. Both report outcome.failure? while carrying no originating axn at all, so test the exception's class rather than the outcome:
rescue StandardError => e
next unless e.is_a?(Axn::Failure)
Rails.logger.warn("rejected by #{e.originating_axn_class}: #{e.originating_result.reason_code}")
endWithin an Axn::Failure, both read nil only when it was built without an action, which axn does when resolving a default message.
Reading originating_result is how you get at values the failing action exposed on its way down — an exposes :reason_code, optional: true set immediately before a fail! is readable here, because expose runs before the fail! that raises. Prefer it to reaching for the action instance: the result reader cannot be shadowed by a field the action happens to declare.
Pattern Matching Support
Axn::Result supports Ruby 3's pattern matching feature, allowing you to destructure results in a more expressive way:
case SomeAction.call
in ok: true, success: String => message, user:, order:
process_success(user, order, message)
in ok: false, error: String => message
handle_error(message)
endAvailable Pattern Matching Keys
When pattern matching, the following keys are available:
ok- Boolean success state (truefor success,falsefor failure)success- Success message string (only present whenokistrue)error- Error message string (only present whenokisfalse)message- Always present message string (success or error)outcome- Symbol indicating the execution outcome (:success,:failure, or:exception)finalized- Boolean indicating if execution completed- Any exposed values from the action
Pattern Matching Examples
Basic Success/Failure Matching:
case result
in ok: true, user: User => user
puts "User created: #{user.name}"
in ok: false, error: String => message
puts "Error: #{message}"
endOutcome-Based Matching:
case result
in ok: true, outcome: :success, data: { id: Integer => id }
puts "Success with ID: #{id}"
in ok: false, outcome: :failure, error: String => message
puts "Business logic failure: #{message}"
in ok: false, outcome: :exception, error: String => message
puts "System error: #{message}"
endComplex Nested Data Matching:
case result
in ok: true, order: { id: Integer => order_id, items: [{ name: String => item_name }] }
puts "Order #{order_id} created with #{item_name}"
in ok: false, error: String => message, field: String => field
puts "Validation failed on #{field}: #{message}"
endType Guards and Variable Binding:
case result
in ok: true, success: String => message, user: { email: String => email }
send_notification(email, message)
in ok: false, error: String => message, code: String => code
log_error(code: code, message: message)
end