axn 0.1.0.pre.alpha.2.8.1 → 0.1.0.pre.alpha.4
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 +4 -4
- data/.cursor/commands/pr.md +36 -0
- data/.cursor/rules/axn-framework-patterns.mdc +43 -0
- data/.cursor/rules/general-coding-standards.mdc +27 -0
- data/.cursor/rules/spec/testing-patterns.mdc +40 -0
- data/CHANGELOG.md +57 -0
- data/Rakefile +114 -4
- data/docs/.vitepress/config.mjs +19 -10
- data/docs/advanced/conventions.md +3 -3
- data/docs/advanced/mountable.md +476 -0
- data/docs/advanced/profiling.md +351 -0
- data/docs/advanced/rough.md +27 -8
- data/docs/index.md +5 -3
- data/docs/intro/about.md +1 -1
- data/docs/intro/overview.md +6 -6
- data/docs/recipes/formatting-context-for-error-tracking.md +186 -0
- data/docs/recipes/memoization.md +103 -18
- data/docs/recipes/rubocop-integration.md +38 -284
- data/docs/recipes/testing.md +14 -14
- data/docs/recipes/validating-user-input.md +1 -1
- data/docs/reference/async.md +429 -0
- data/docs/reference/axn-result.md +107 -0
- data/docs/reference/class.md +225 -64
- data/docs/reference/configuration.md +366 -34
- data/docs/reference/form-object.md +252 -0
- data/docs/reference/instance.md +14 -29
- data/docs/strategies/client.md +212 -0
- data/docs/strategies/form.md +235 -0
- data/docs/strategies/index.md +21 -21
- data/docs/strategies/transaction.md +1 -1
- data/docs/usage/setup.md +16 -2
- data/docs/usage/steps.md +7 -7
- data/docs/usage/using.md +23 -12
- data/docs/usage/writing.md +191 -12
- data/lib/axn/async/adapters/active_job.rb +74 -0
- data/lib/axn/async/adapters/disabled.rb +41 -0
- data/lib/axn/async/adapters/sidekiq.rb +67 -0
- data/lib/axn/async/adapters.rb +26 -0
- data/lib/axn/async/batch_enqueue/config.rb +38 -0
- data/lib/axn/async/batch_enqueue.rb +99 -0
- data/lib/axn/async/enqueue_all_orchestrator.rb +363 -0
- data/lib/axn/async.rb +178 -0
- data/lib/axn/configuration.rb +113 -0
- data/lib/{action → axn}/context.rb +22 -4
- data/lib/axn/core/automatic_logging.rb +89 -0
- data/lib/axn/core/context/facade.rb +69 -0
- data/lib/{action → axn}/core/context/facade_inspector.rb +32 -5
- data/lib/{action → axn}/core/context/internal.rb +5 -5
- data/lib/{action → axn}/core/contract.rb +111 -73
- data/lib/{action → axn}/core/contract_for_subfields.rb +30 -35
- data/lib/{action → axn}/core/contract_validation.rb +27 -12
- data/lib/axn/core/contract_validation_for_subfields.rb +165 -0
- data/lib/axn/core/default_call.rb +63 -0
- data/lib/axn/core/field_resolvers/extract.rb +32 -0
- data/lib/axn/core/field_resolvers/model.rb +63 -0
- data/lib/axn/core/field_resolvers.rb +24 -0
- data/lib/{action → axn}/core/flow/callbacks.rb +7 -7
- data/lib/{action → axn}/core/flow/exception_execution.rb +9 -13
- data/lib/{action → axn}/core/flow/handlers/base_descriptor.rb +3 -2
- data/lib/{action → axn}/core/flow/handlers/descriptors/callback_descriptor.rb +2 -2
- data/lib/{action → axn}/core/flow/handlers/descriptors/message_descriptor.rb +23 -11
- data/lib/axn/core/flow/handlers/invoker.rb +47 -0
- data/lib/{action → axn}/core/flow/handlers/matcher.rb +9 -19
- data/lib/{action → axn}/core/flow/handlers/registry.rb +3 -1
- data/lib/{action → axn}/core/flow/handlers/resolvers/base_resolver.rb +1 -1
- data/lib/{action → axn}/core/flow/handlers/resolvers/callback_resolver.rb +2 -2
- data/lib/{action → axn}/core/flow/handlers/resolvers/message_resolver.rb +12 -3
- data/lib/axn/core/flow/handlers.rb +20 -0
- data/lib/{action → axn}/core/flow/messages.rb +8 -8
- data/lib/{action → axn}/core/flow.rb +4 -4
- data/lib/{action → axn}/core/hooks.rb +17 -5
- data/lib/axn/core/logging.rb +48 -0
- data/lib/axn/core/memoization.rb +53 -0
- data/lib/{action → axn}/core/nesting_tracking.rb +1 -1
- data/lib/{action → axn}/core/timing.rb +1 -1
- data/lib/axn/core/tracing.rb +90 -0
- data/lib/axn/core/use_strategy.rb +29 -0
- data/lib/{action → axn}/core/validation/fields.rb +26 -2
- data/lib/{action → axn}/core/validation/subfields.rb +14 -12
- data/lib/axn/core/validation/validators/model_validator.rb +36 -0
- data/lib/axn/core/validation/validators/type_validator.rb +80 -0
- data/lib/{action → axn}/core/validation/validators/validate_validator.rb +12 -2
- data/lib/{action → axn}/core.rb +55 -55
- data/lib/{action → axn}/exceptions.rb +12 -2
- data/lib/axn/extras/strategies/client.rb +150 -0
- data/lib/axn/extras/strategies/vernier.rb +121 -0
- data/lib/axn/extras.rb +4 -0
- data/lib/axn/factory.rb +122 -34
- data/lib/axn/form_object.rb +90 -0
- data/lib/axn/internal/logging.rb +30 -0
- data/lib/axn/internal/registry.rb +87 -0
- data/lib/axn/mountable/descriptor.rb +76 -0
- data/lib/axn/mountable/helpers/class_builder.rb +193 -0
- data/lib/axn/mountable/helpers/mounter.rb +33 -0
- data/lib/axn/mountable/helpers/namespace_manager.rb +38 -0
- data/lib/axn/mountable/helpers/validator.rb +112 -0
- data/lib/axn/mountable/inherit_profiles.rb +72 -0
- data/lib/axn/mountable/mounting_strategies/_base.rb +87 -0
- data/lib/axn/mountable/mounting_strategies/axn.rb +48 -0
- data/lib/axn/mountable/mounting_strategies/method.rb +95 -0
- data/lib/axn/mountable/mounting_strategies/step.rb +69 -0
- data/lib/axn/mountable/mounting_strategies.rb +32 -0
- data/lib/axn/mountable.rb +119 -0
- data/lib/axn/rails/engine.rb +51 -0
- data/lib/axn/rails/generators/axn_generator.rb +86 -0
- data/lib/axn/rails/generators/templates/action.rb.erb +17 -0
- data/lib/axn/rails/generators/templates/action_spec.rb.erb +25 -0
- data/lib/{action → axn}/result.rb +32 -13
- data/lib/axn/strategies/form.rb +98 -0
- data/lib/axn/strategies/transaction.rb +26 -0
- data/lib/axn/strategies.rb +20 -0
- data/lib/axn/testing/spec_helpers.rb +6 -8
- data/lib/axn/util/callable.rb +120 -0
- data/lib/axn/util/contract_error_handling.rb +32 -0
- data/lib/axn/util/execution_context.rb +34 -0
- data/lib/axn/util/global_id_serialization.rb +52 -0
- data/lib/axn/util/logging.rb +87 -0
- data/lib/axn/util/memoization.rb +20 -0
- data/lib/axn/version.rb +1 -1
- data/lib/axn.rb +26 -16
- data/lib/rubocop/cop/axn/README.md +23 -23
- data/lib/rubocop/cop/axn/unchecked_result.rb +138 -17
- metadata +106 -64
- data/.rspec +0 -3
- data/.rubocop.yml +0 -76
- data/.tool-versions +0 -1
- data/docs/reference/action-result.md +0 -37
- data/lib/action/attachable/base.rb +0 -43
- data/lib/action/attachable/steps.rb +0 -63
- data/lib/action/attachable/subactions.rb +0 -70
- data/lib/action/attachable.rb +0 -17
- data/lib/action/configuration.rb +0 -55
- data/lib/action/core/automatic_logging.rb +0 -93
- data/lib/action/core/context/facade.rb +0 -48
- data/lib/action/core/flow/handlers/invoker.rb +0 -73
- data/lib/action/core/flow/handlers.rb +0 -20
- data/lib/action/core/logging.rb +0 -37
- data/lib/action/core/tracing.rb +0 -17
- data/lib/action/core/use_strategy.rb +0 -30
- data/lib/action/core/validation/validators/model_validator.rb +0 -34
- data/lib/action/core/validation/validators/type_validator.rb +0 -30
- data/lib/action/enqueueable/via_sidekiq.rb +0 -76
- data/lib/action/enqueueable.rb +0 -13
- data/lib/action/strategies/transaction.rb +0 -19
- data/lib/action/strategies.rb +0 -48
- data/lib/axn/util.rb +0 -24
- data/package.json +0 -10
- data/yarn.lock +0 -1166
data/docs/reference/class.md
CHANGED
|
@@ -13,9 +13,10 @@ Both `expects` and `exposes` support the same core options:
|
|
|
13
13
|
| Option | Example (same for `exposes`) | Meaning |
|
|
14
14
|
| -- | -- | -- |
|
|
15
15
|
| `sensitive` | `expects :password, sensitive: true` | Filters the field's value when logging, reporting errors, or calling `inspect`
|
|
16
|
-
| `default` | `expects :foo, default: 123` | If `foo`
|
|
17
|
-
| `
|
|
18
|
-
| `
|
|
16
|
+
| `default` | `expects :foo, default: 123` | If `foo` is missing or explicitly `nil`, it'll default to this value (not applied for blank values)
|
|
17
|
+
| `optional` | `expects :foo, optional: true` | **Recommended**: Don't fail if the value is missing, nil, or blank. Equivalent to `allow_blank: true`
|
|
18
|
+
| `allow_nil` | `expects :foo, allow_nil: true` | Don't fail if the value is `nil` (but will fail for blank strings)
|
|
19
|
+
| `allow_blank` | `expects :foo, allow_blank: true` | Don't fail if the value is blank (nil, empty string, whitespace, etc.)
|
|
19
20
|
| `type` | `expects :foo, type: String` | Custom type validation -- fail unless `name.is_a?(String)`
|
|
20
21
|
| anything else | `expects :foo, inclusion: { in: [:apple, :peach] }` | Any other arguments will be processed [as ActiveModel validations](https://guides.rubyonrails.org/active_record_validations.html) (i.e. as if passed to `validates :foo, <...>` on an ActiveRecord model)
|
|
21
22
|
|
|
@@ -26,33 +27,52 @@ Both `expects` and `exposes` support the same core options:
|
|
|
26
27
|
While we _support_ complex interface validations, in practice you usually just want a `type`, if anything. Remember this is your validation about how the action is called, _not_ pretty user-facing errors (there's [a different pattern for that](/recipes/validating-user-input)).
|
|
27
28
|
:::
|
|
28
29
|
|
|
29
|
-
In addition to the [standard ActiveModel validations](https://guides.rubyonrails.org/active_record_validations.html), we also support
|
|
30
|
+
In addition to the [standard ActiveModel validations](https://guides.rubyonrails.org/active_record_validations.html), we also support four additional custom validators:
|
|
30
31
|
* `type: Foo` - fails unless the provided value `.is_a?(Foo)`
|
|
31
32
|
* Edge case: use `type: :boolean` to handle a boolean field (since ruby doesn't have a Boolean class to pass in directly)
|
|
32
33
|
* Edge case: use `type: :uuid` to handle a confirming given string is a UUID (with or without `-` chars)
|
|
34
|
+
* Edge case: use `type: :params` to accept either a Hash or ActionController::Parameters (Rails-compatible)
|
|
33
35
|
* `validate: [callable]` - Support custom validations (fails if any string is returned OR if it raises an exception)
|
|
34
36
|
* Example:
|
|
35
37
|
```ruby
|
|
36
38
|
expects :foo, validate: ->(value) { "must be pretty big" unless value > 10 }
|
|
37
39
|
```
|
|
38
|
-
* `model: true` (or `model: TheModelClass`) - allows auto-hydrating a record when only given its ID
|
|
40
|
+
* `model: true` (or `model: TheModelClass` or `model: { klass: TheModelClass, finder: :find }`) - allows auto-hydrating a record when only given its ID
|
|
39
41
|
* Example:
|
|
40
42
|
```ruby
|
|
41
|
-
expects :
|
|
43
|
+
expects :user, model: true
|
|
44
|
+
# or
|
|
45
|
+
expects :user, model: User
|
|
46
|
+
# or with custom finder
|
|
47
|
+
expects :user, model: { klass: User, finder: :find }
|
|
42
48
|
```
|
|
43
49
|
This line will add expectations that:
|
|
44
|
-
* `user_id` is provided
|
|
45
|
-
* `User.find(user_id)` returns a record
|
|
50
|
+
* `user_id` is provided (automatically derived from field name)
|
|
51
|
+
* `User.find(user_id)` (or custom finder) returns a record
|
|
46
52
|
|
|
47
|
-
And, when used on `expects`, will create
|
|
48
|
-
* `
|
|
49
|
-
* `user` (for the auto-found record)
|
|
53
|
+
And, when used on `expects`, will create a reader method for you:
|
|
54
|
+
* `user` (the auto-found record)
|
|
50
55
|
|
|
51
56
|
::: info NOTES
|
|
52
|
-
* The
|
|
53
|
-
*
|
|
57
|
+
* The system automatically looks for `#{field}_id` (e.g., `:user` → `:user_id`)
|
|
58
|
+
* The `klass` option defaults to the field name classified (e.g., `:user` → `User`)
|
|
59
|
+
* The `finder` option defaults to `:find` but can be any method that takes an ID directly
|
|
60
|
+
* This works with any class that has a finder method (e.g., `User.find`, `ApiService.find_by_id`, etc.)
|
|
61
|
+
* For external APIs, you can pass a `Method` object as the finder
|
|
54
62
|
:::
|
|
55
63
|
|
|
64
|
+
#### How `optional`, `allow_blank` and `allow_nil` work with validators
|
|
65
|
+
|
|
66
|
+
When you specify `optional: true`, `allow_blank: true`, or `allow_nil: true` on a field, these options are automatically passed through to **all validators** applied to that field. This means:
|
|
67
|
+
|
|
68
|
+
- **ActiveModel validations** (like `inclusion`, `length`, etc.) will respect these options
|
|
69
|
+
- **Custom validators** (`type`, `validate`, `model`) will also respect these options
|
|
70
|
+
- **Type validator edge case**: Note passing `allow_blank` is nonsensical for type: :params and type: :boolean
|
|
71
|
+
|
|
72
|
+
**Recommended approach**: Use `optional: true` instead of `allow_blank: true` for better clarity. The `optional` parameter is equivalent to `allow_blank: true` and makes the intent clearer.
|
|
73
|
+
|
|
74
|
+
If neither `optional`, `allow_blank` nor `allow_nil` is specified, a default presence validation is automatically added (unless the type is `:boolean` or `:params`, which have their own validation logic as described above).
|
|
75
|
+
|
|
56
76
|
### Details specific to `.exposes`
|
|
57
77
|
|
|
58
78
|
Remember that you'll need [a corresponding `expose` call](/reference/instance#expose) for every variable you declare via `exposes`.
|
|
@@ -62,13 +82,14 @@ Remember that you'll need [a corresponding `expose` call](/reference/instance#ex
|
|
|
62
82
|
|
|
63
83
|
#### Nested/Subfield expectations
|
|
64
84
|
|
|
65
|
-
`expects` is for defining the inbound interface. Usually it's enough to declare the top-level fields you receive, but sometimes you want to make expectations about the shape of that data, and/or to define easy accessor methods for deeply nested fields. `expects` supports the `on` option for this (all the normal attributes can be applied as well
|
|
85
|
+
`expects` is for defining the inbound interface. Usually it's enough to declare the top-level fields you receive, but sometimes you want to make expectations about the shape of that data, and/or to define easy accessor methods for deeply nested fields. `expects` supports the `on` option for this (all the normal attributes can be applied as well):
|
|
66
86
|
|
|
67
87
|
```ruby
|
|
68
88
|
class Foo
|
|
69
89
|
expects :event
|
|
70
90
|
expects :data, type: Hash, on: :event # [!code focus:2]
|
|
71
91
|
expects :some, :random, :fields, on: :data
|
|
92
|
+
expects :optional_field, on: :data, default: "default value" # [!code focus]
|
|
72
93
|
|
|
73
94
|
def call
|
|
74
95
|
puts "THe event.data.random field's value is: #{random}"
|
|
@@ -76,8 +97,12 @@ class Foo
|
|
|
76
97
|
end
|
|
77
98
|
```
|
|
78
99
|
|
|
100
|
+
::: tip Subfield Defaults
|
|
101
|
+
Defaults work the same way for subfields as they do for top-level fields - they are applied when the subfield is missing or explicitly `nil`, but not for blank values.
|
|
102
|
+
:::
|
|
103
|
+
|
|
79
104
|
#### `preprocess`
|
|
80
|
-
`expects` also supports a `preprocess` option that, if set to a callable, will be executed _before_ applying any validations. This can be useful for type coercion, e.g.:
|
|
105
|
+
`expects` also supports a `preprocess` option that, if set to a callable, will be executed _before_ applying any defaults or validations. This can be useful for type coercion, e.g.:
|
|
81
106
|
|
|
82
107
|
```ruby
|
|
83
108
|
expects :date, type: Date, preprocess: ->(d) { d.is_a?(Date) ? d : Date.parse(d) }
|
|
@@ -124,46 +149,74 @@ def build_error_message(exception:)
|
|
|
124
149
|
end
|
|
125
150
|
```
|
|
126
151
|
|
|
127
|
-
|
|
128
|
-
|
|
152
|
+
## Message Matching Order {#message-matching-order}
|
|
153
|
+
|
|
154
|
+
::: danger Important: Understanding Handler Evaluation Order
|
|
155
|
+
Message handlers are stored in **last-defined-first** order and evaluated in that order until a match is found. This has critical implications for how you structure your message declarations.
|
|
156
|
+
:::
|
|
157
|
+
|
|
158
|
+
### How It Works
|
|
159
|
+
|
|
160
|
+
1. Handlers are registered in reverse definition order (last defined = first evaluated)
|
|
161
|
+
2. The system evaluates handlers one by one until finding one that matches
|
|
162
|
+
3. Static handlers (no `if:` or `unless:` condition) **always match**
|
|
163
|
+
4. Once a match is found, evaluation stops
|
|
164
|
+
|
|
165
|
+
### Correct Pattern: Static Fallbacks First
|
|
166
|
+
|
|
167
|
+
Because static handlers always match, they must be defined **first** (so they're evaluated **last**):
|
|
129
168
|
|
|
130
|
-
**Correct order:**
|
|
131
169
|
```ruby
|
|
132
170
|
class MyAction
|
|
133
|
-
include
|
|
171
|
+
include Axn
|
|
134
172
|
|
|
135
|
-
# Define static fallback
|
|
136
|
-
|
|
137
|
-
error "Default error message"
|
|
173
|
+
# 1. Define static fallback FIRST (evaluated last, catches anything unmatched)
|
|
174
|
+
error "Something went wrong"
|
|
138
175
|
|
|
139
|
-
#
|
|
140
|
-
|
|
141
|
-
error "
|
|
176
|
+
# 2. Define conditional handlers AFTER (evaluated first, catch specific cases)
|
|
177
|
+
error "Invalid input provided", if: ArgumentError
|
|
178
|
+
error "Record not found", if: ActiveRecord::RecordNotFound
|
|
142
179
|
end
|
|
143
180
|
```
|
|
144
181
|
|
|
145
|
-
|
|
182
|
+
### Incorrect Pattern: Conditional Messages Shadowed
|
|
183
|
+
|
|
184
|
+
If you define static handlers last, they match first and conditional handlers are never reached:
|
|
185
|
+
|
|
146
186
|
```ruby
|
|
147
187
|
class MyAction
|
|
148
|
-
include
|
|
188
|
+
include Axn
|
|
149
189
|
|
|
150
|
-
# These
|
|
151
|
-
|
|
152
|
-
error "
|
|
190
|
+
# These will NEVER be reached!
|
|
191
|
+
error "Invalid input provided", if: ArgumentError
|
|
192
|
+
error "Record not found", if: ActiveRecord::RecordNotFound
|
|
153
193
|
|
|
154
|
-
#
|
|
155
|
-
|
|
156
|
-
|
|
194
|
+
# This static handler is evaluated FIRST and always matches
|
|
195
|
+
error "Something went wrong"
|
|
196
|
+
end
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### With Inheritance
|
|
200
|
+
|
|
201
|
+
Child class handlers are evaluated before parent class handlers:
|
|
202
|
+
|
|
203
|
+
```ruby
|
|
204
|
+
class ParentAction
|
|
205
|
+
include Axn
|
|
206
|
+
error "Parent error" # Evaluated last
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
class ChildAction < ParentAction
|
|
210
|
+
error "Child error" # Evaluated first
|
|
157
211
|
end
|
|
158
212
|
```
|
|
159
|
-
:::
|
|
160
213
|
|
|
161
214
|
## Conditional messages
|
|
162
215
|
|
|
163
216
|
While `.error` and `.success` set the default messages, you can register conditional messages using an optional `if:` or `unless:` matcher. The matcher can be:
|
|
164
217
|
|
|
165
218
|
- an exception class (e.g., `ArgumentError`)
|
|
166
|
-
- a class name string (e.g., `"
|
|
219
|
+
- a class name string (e.g., `"Axn::InboundValidationError"`)
|
|
167
220
|
- a symbol referencing a local instance method predicate (arity 0 or 1, or keyword `exception:`), e.g. `:bad_input?`
|
|
168
221
|
- a callable (arity 0 or 1, or keyword `exception:`)
|
|
169
222
|
|
|
@@ -226,9 +279,13 @@ The `from:` parameter allows you to customize error messages when an action call
|
|
|
226
279
|
|
|
227
280
|
When using `from:`, the error handler receives the exception from the child action, and you can access the child's error message via `e.message` (which contains the `result.error` from the child action).
|
|
228
281
|
|
|
282
|
+
### Basic usage
|
|
283
|
+
|
|
284
|
+
You can use `from:` with a single child action class. The prefix and custom handler are optional:
|
|
285
|
+
|
|
229
286
|
```ruby
|
|
230
287
|
class InnerAction
|
|
231
|
-
include
|
|
288
|
+
include Axn
|
|
232
289
|
|
|
233
290
|
error "Something went wrong in the inner action"
|
|
234
291
|
|
|
@@ -238,9 +295,12 @@ class InnerAction
|
|
|
238
295
|
end
|
|
239
296
|
|
|
240
297
|
class OuterAction
|
|
241
|
-
include
|
|
298
|
+
include Axn
|
|
299
|
+
|
|
300
|
+
# Simply inherit child's error message (no prefix or custom handler needed)
|
|
301
|
+
error from: InnerAction
|
|
242
302
|
|
|
243
|
-
#
|
|
303
|
+
# Or customize the message
|
|
244
304
|
error from: InnerAction do |e|
|
|
245
305
|
"Outer action failed: #{e.message}"
|
|
246
306
|
end
|
|
@@ -254,7 +314,58 @@ end
|
|
|
254
314
|
In this example:
|
|
255
315
|
- When `InnerAction` fails, `OuterAction` will catch the exception
|
|
256
316
|
- The `e.message` contains the error message from `InnerAction`'s result
|
|
257
|
-
-
|
|
317
|
+
- With no handler: the error message will be "Something went wrong in the inner action" (inherited directly)
|
|
318
|
+
- With custom handler: the error message will be "Outer action failed: Something went wrong in the inner action"
|
|
319
|
+
|
|
320
|
+
### Matching multiple child actions
|
|
321
|
+
|
|
322
|
+
You can pass an array of child action classes to match multiple children:
|
|
323
|
+
|
|
324
|
+
```ruby
|
|
325
|
+
class OuterAction
|
|
326
|
+
include Axn
|
|
327
|
+
|
|
328
|
+
# Match errors from multiple child actions
|
|
329
|
+
error from: [FirstChildAction, SecondChildAction]
|
|
330
|
+
|
|
331
|
+
# Or with custom handler
|
|
332
|
+
error from: [FirstChildAction, SecondChildAction] do |e|
|
|
333
|
+
"Parent caught: #{e.message}"
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
def call
|
|
337
|
+
# Calls one of the child actions
|
|
338
|
+
end
|
|
339
|
+
end
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
You can also mix class references and string class names:
|
|
343
|
+
|
|
344
|
+
```ruby
|
|
345
|
+
error from: [FirstChildAction, "SecondChildAction"]
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
### Matching any child action
|
|
349
|
+
|
|
350
|
+
Use `from: true` to match errors from any child action without listing them explicitly:
|
|
351
|
+
|
|
352
|
+
```ruby
|
|
353
|
+
class OuterAction
|
|
354
|
+
include Axn
|
|
355
|
+
|
|
356
|
+
# Match errors from any child action
|
|
357
|
+
error from: true
|
|
358
|
+
|
|
359
|
+
# Or with custom handler
|
|
360
|
+
error from: true do |e|
|
|
361
|
+
"Any child failed: #{e.message}"
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
def call
|
|
365
|
+
# Can call any child action
|
|
366
|
+
end
|
|
367
|
+
end
|
|
368
|
+
```
|
|
258
369
|
|
|
259
370
|
This pattern is especially useful for:
|
|
260
371
|
- Adding context to error messages from sub-actions
|
|
@@ -267,7 +378,7 @@ You can also combine the `from:` parameter with the `prefix:` keyword to create
|
|
|
267
378
|
|
|
268
379
|
```ruby
|
|
269
380
|
class OuterAction
|
|
270
|
-
include
|
|
381
|
+
include Axn
|
|
271
382
|
|
|
272
383
|
# Add prefix to error messages from InnerAction
|
|
273
384
|
error from: InnerAction, prefix: "API Error: " do |e|
|
|
@@ -287,45 +398,95 @@ This results in:
|
|
|
287
398
|
- With custom message: "API Error: Request failed: Something went wrong in the inner action"
|
|
288
399
|
- With prefix only: "API Error: Something went wrong in the inner action"
|
|
289
400
|
|
|
290
|
-
### Message ordering
|
|
401
|
+
### Message ordering with `from:`
|
|
291
402
|
|
|
292
|
-
|
|
403
|
+
When using `from:` with inheritance, the same [message matching order](#message-matching-order) applies. Define your `from:` handlers after your static fallback:
|
|
293
404
|
|
|
294
405
|
```ruby
|
|
295
|
-
class
|
|
296
|
-
include
|
|
406
|
+
class OuterAction
|
|
407
|
+
include Axn
|
|
297
408
|
|
|
298
|
-
|
|
299
|
-
error "
|
|
300
|
-
end
|
|
409
|
+
# Static fallback first
|
|
410
|
+
error "Something went wrong"
|
|
301
411
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
error
|
|
412
|
+
# Then from: handlers for specific child actions
|
|
413
|
+
error from: InnerAction, prefix: "Inner failed: "
|
|
414
|
+
error from: AnotherAction, prefix: "Another failed: "
|
|
305
415
|
end
|
|
306
416
|
```
|
|
307
417
|
|
|
308
|
-
|
|
418
|
+
## `.async`
|
|
419
|
+
|
|
420
|
+
Configures the async execution behavior for the action. This determines how the action will be executed when `call_async` is called.
|
|
309
421
|
|
|
310
422
|
```ruby
|
|
311
423
|
class MyAction
|
|
312
|
-
include
|
|
424
|
+
include Axn
|
|
425
|
+
|
|
426
|
+
# Configure Sidekiq
|
|
427
|
+
async :sidekiq do
|
|
428
|
+
sidekiq_options queue: "high_priority", retry: 5, priority: 10
|
|
429
|
+
end
|
|
313
430
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
431
|
+
# Or use keyword arguments (shorthand)
|
|
432
|
+
async :sidekiq, queue: "high_priority", retry: 5
|
|
433
|
+
|
|
434
|
+
# Configure ActiveJob
|
|
435
|
+
async :active_job do
|
|
436
|
+
queue_as "data_processing"
|
|
437
|
+
self.priority = 10
|
|
438
|
+
self.wait = 5.minutes
|
|
439
|
+
end
|
|
317
440
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
441
|
+
# Disable async execution
|
|
442
|
+
async false
|
|
443
|
+
|
|
444
|
+
expects :input
|
|
445
|
+
|
|
446
|
+
def call
|
|
447
|
+
# Action logic here
|
|
448
|
+
end
|
|
321
449
|
end
|
|
322
450
|
```
|
|
323
451
|
|
|
324
|
-
|
|
325
|
-
The system evaluates handlers in the order they were defined until it finds one that matches and doesn't raise an exception. If a handler raises an exception, it falls back to the next matching handler, then to static messages, and finally to the default message.
|
|
452
|
+
### Available Adapters
|
|
326
453
|
|
|
327
|
-
|
|
328
|
-
|
|
454
|
+
**`:sidekiq`** - Integrates with Sidekiq background job processing
|
|
455
|
+
- Supports all Sidekiq configuration options via `sidekiq_options`
|
|
456
|
+
- Supports keyword argument shorthand for common options (`queue`, `retry`, `priority`)
|
|
457
|
+
|
|
458
|
+
**`:active_job`** - Integrates with Rails' ActiveJob framework
|
|
459
|
+
- Supports all ActiveJob configuration options
|
|
460
|
+
- Works with any ActiveJob backend (Sidekiq, Delayed Job, etc.)
|
|
461
|
+
|
|
462
|
+
**`false`** - Disables async execution
|
|
463
|
+
- `call_async` will raise a `NotImplementedError`
|
|
464
|
+
|
|
465
|
+
### Inheritance
|
|
466
|
+
|
|
467
|
+
Async configuration is inherited from parent classes. Child classes can override the parent's configuration:
|
|
468
|
+
|
|
469
|
+
```ruby
|
|
470
|
+
class ParentAction
|
|
471
|
+
include Axn
|
|
472
|
+
|
|
473
|
+
async :sidekiq do
|
|
474
|
+
sidekiq_options queue: "parent_queue"
|
|
475
|
+
end
|
|
476
|
+
end
|
|
477
|
+
|
|
478
|
+
class ChildAction < ParentAction
|
|
479
|
+
# Inherits parent's Sidekiq configuration
|
|
480
|
+
# Can override with its own configuration
|
|
481
|
+
async :active_job do
|
|
482
|
+
queue_as "child_queue"
|
|
483
|
+
end
|
|
484
|
+
end
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
### Default Configuration
|
|
488
|
+
|
|
489
|
+
If no async configuration is specified, the action will use the default configuration set via `Axn.config.set_default_async`. If no default is set, async execution is disabled.
|
|
329
490
|
|
|
330
491
|
## Callbacks
|
|
331
492
|
|
|
@@ -371,7 +532,7 @@ Much like the [globally-configured on_exception hook](/reference/configuration#o
|
|
|
371
532
|
|
|
372
533
|
```ruby
|
|
373
534
|
class Foo
|
|
374
|
-
include
|
|
535
|
+
include Axn
|
|
375
536
|
|
|
376
537
|
on_exception do |exception| # [!code focus:3]
|
|
377
538
|
# e.g. trigger a slack error
|
|
@@ -383,7 +544,7 @@ Note that by default the `on_exception` block will be applied to _any_ `Standard
|
|
|
383
544
|
|
|
384
545
|
```ruby
|
|
385
546
|
class Foo
|
|
386
|
-
include
|
|
547
|
+
include Axn
|
|
387
548
|
|
|
388
549
|
on_exception(if: NoMethodError) do |exception| # [!code focus]
|
|
389
550
|
# e.g. trigger a slack error
|