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.
Files changed (148) hide show
  1. checksums.yaml +4 -4
  2. data/.cursor/commands/pr.md +36 -0
  3. data/.cursor/rules/axn-framework-patterns.mdc +43 -0
  4. data/.cursor/rules/general-coding-standards.mdc +27 -0
  5. data/.cursor/rules/spec/testing-patterns.mdc +40 -0
  6. data/CHANGELOG.md +57 -0
  7. data/Rakefile +114 -4
  8. data/docs/.vitepress/config.mjs +19 -10
  9. data/docs/advanced/conventions.md +3 -3
  10. data/docs/advanced/mountable.md +476 -0
  11. data/docs/advanced/profiling.md +351 -0
  12. data/docs/advanced/rough.md +27 -8
  13. data/docs/index.md +5 -3
  14. data/docs/intro/about.md +1 -1
  15. data/docs/intro/overview.md +6 -6
  16. data/docs/recipes/formatting-context-for-error-tracking.md +186 -0
  17. data/docs/recipes/memoization.md +103 -18
  18. data/docs/recipes/rubocop-integration.md +38 -284
  19. data/docs/recipes/testing.md +14 -14
  20. data/docs/recipes/validating-user-input.md +1 -1
  21. data/docs/reference/async.md +429 -0
  22. data/docs/reference/axn-result.md +107 -0
  23. data/docs/reference/class.md +225 -64
  24. data/docs/reference/configuration.md +366 -34
  25. data/docs/reference/form-object.md +252 -0
  26. data/docs/reference/instance.md +14 -29
  27. data/docs/strategies/client.md +212 -0
  28. data/docs/strategies/form.md +235 -0
  29. data/docs/strategies/index.md +21 -21
  30. data/docs/strategies/transaction.md +1 -1
  31. data/docs/usage/setup.md +16 -2
  32. data/docs/usage/steps.md +7 -7
  33. data/docs/usage/using.md +23 -12
  34. data/docs/usage/writing.md +191 -12
  35. data/lib/axn/async/adapters/active_job.rb +74 -0
  36. data/lib/axn/async/adapters/disabled.rb +41 -0
  37. data/lib/axn/async/adapters/sidekiq.rb +67 -0
  38. data/lib/axn/async/adapters.rb +26 -0
  39. data/lib/axn/async/batch_enqueue/config.rb +38 -0
  40. data/lib/axn/async/batch_enqueue.rb +99 -0
  41. data/lib/axn/async/enqueue_all_orchestrator.rb +363 -0
  42. data/lib/axn/async.rb +178 -0
  43. data/lib/axn/configuration.rb +113 -0
  44. data/lib/{action → axn}/context.rb +22 -4
  45. data/lib/axn/core/automatic_logging.rb +89 -0
  46. data/lib/axn/core/context/facade.rb +69 -0
  47. data/lib/{action → axn}/core/context/facade_inspector.rb +32 -5
  48. data/lib/{action → axn}/core/context/internal.rb +5 -5
  49. data/lib/{action → axn}/core/contract.rb +111 -73
  50. data/lib/{action → axn}/core/contract_for_subfields.rb +30 -35
  51. data/lib/{action → axn}/core/contract_validation.rb +27 -12
  52. data/lib/axn/core/contract_validation_for_subfields.rb +165 -0
  53. data/lib/axn/core/default_call.rb +63 -0
  54. data/lib/axn/core/field_resolvers/extract.rb +32 -0
  55. data/lib/axn/core/field_resolvers/model.rb +63 -0
  56. data/lib/axn/core/field_resolvers.rb +24 -0
  57. data/lib/{action → axn}/core/flow/callbacks.rb +7 -7
  58. data/lib/{action → axn}/core/flow/exception_execution.rb +9 -13
  59. data/lib/{action → axn}/core/flow/handlers/base_descriptor.rb +3 -2
  60. data/lib/{action → axn}/core/flow/handlers/descriptors/callback_descriptor.rb +2 -2
  61. data/lib/{action → axn}/core/flow/handlers/descriptors/message_descriptor.rb +23 -11
  62. data/lib/axn/core/flow/handlers/invoker.rb +47 -0
  63. data/lib/{action → axn}/core/flow/handlers/matcher.rb +9 -19
  64. data/lib/{action → axn}/core/flow/handlers/registry.rb +3 -1
  65. data/lib/{action → axn}/core/flow/handlers/resolvers/base_resolver.rb +1 -1
  66. data/lib/{action → axn}/core/flow/handlers/resolvers/callback_resolver.rb +2 -2
  67. data/lib/{action → axn}/core/flow/handlers/resolvers/message_resolver.rb +12 -3
  68. data/lib/axn/core/flow/handlers.rb +20 -0
  69. data/lib/{action → axn}/core/flow/messages.rb +8 -8
  70. data/lib/{action → axn}/core/flow.rb +4 -4
  71. data/lib/{action → axn}/core/hooks.rb +17 -5
  72. data/lib/axn/core/logging.rb +48 -0
  73. data/lib/axn/core/memoization.rb +53 -0
  74. data/lib/{action → axn}/core/nesting_tracking.rb +1 -1
  75. data/lib/{action → axn}/core/timing.rb +1 -1
  76. data/lib/axn/core/tracing.rb +90 -0
  77. data/lib/axn/core/use_strategy.rb +29 -0
  78. data/lib/{action → axn}/core/validation/fields.rb +26 -2
  79. data/lib/{action → axn}/core/validation/subfields.rb +14 -12
  80. data/lib/axn/core/validation/validators/model_validator.rb +36 -0
  81. data/lib/axn/core/validation/validators/type_validator.rb +80 -0
  82. data/lib/{action → axn}/core/validation/validators/validate_validator.rb +12 -2
  83. data/lib/{action → axn}/core.rb +55 -55
  84. data/lib/{action → axn}/exceptions.rb +12 -2
  85. data/lib/axn/extras/strategies/client.rb +150 -0
  86. data/lib/axn/extras/strategies/vernier.rb +121 -0
  87. data/lib/axn/extras.rb +4 -0
  88. data/lib/axn/factory.rb +122 -34
  89. data/lib/axn/form_object.rb +90 -0
  90. data/lib/axn/internal/logging.rb +30 -0
  91. data/lib/axn/internal/registry.rb +87 -0
  92. data/lib/axn/mountable/descriptor.rb +76 -0
  93. data/lib/axn/mountable/helpers/class_builder.rb +193 -0
  94. data/lib/axn/mountable/helpers/mounter.rb +33 -0
  95. data/lib/axn/mountable/helpers/namespace_manager.rb +38 -0
  96. data/lib/axn/mountable/helpers/validator.rb +112 -0
  97. data/lib/axn/mountable/inherit_profiles.rb +72 -0
  98. data/lib/axn/mountable/mounting_strategies/_base.rb +87 -0
  99. data/lib/axn/mountable/mounting_strategies/axn.rb +48 -0
  100. data/lib/axn/mountable/mounting_strategies/method.rb +95 -0
  101. data/lib/axn/mountable/mounting_strategies/step.rb +69 -0
  102. data/lib/axn/mountable/mounting_strategies.rb +32 -0
  103. data/lib/axn/mountable.rb +119 -0
  104. data/lib/axn/rails/engine.rb +51 -0
  105. data/lib/axn/rails/generators/axn_generator.rb +86 -0
  106. data/lib/axn/rails/generators/templates/action.rb.erb +17 -0
  107. data/lib/axn/rails/generators/templates/action_spec.rb.erb +25 -0
  108. data/lib/{action → axn}/result.rb +32 -13
  109. data/lib/axn/strategies/form.rb +98 -0
  110. data/lib/axn/strategies/transaction.rb +26 -0
  111. data/lib/axn/strategies.rb +20 -0
  112. data/lib/axn/testing/spec_helpers.rb +6 -8
  113. data/lib/axn/util/callable.rb +120 -0
  114. data/lib/axn/util/contract_error_handling.rb +32 -0
  115. data/lib/axn/util/execution_context.rb +34 -0
  116. data/lib/axn/util/global_id_serialization.rb +52 -0
  117. data/lib/axn/util/logging.rb +87 -0
  118. data/lib/axn/util/memoization.rb +20 -0
  119. data/lib/axn/version.rb +1 -1
  120. data/lib/axn.rb +26 -16
  121. data/lib/rubocop/cop/axn/README.md +23 -23
  122. data/lib/rubocop/cop/axn/unchecked_result.rb +138 -17
  123. metadata +106 -64
  124. data/.rspec +0 -3
  125. data/.rubocop.yml +0 -76
  126. data/.tool-versions +0 -1
  127. data/docs/reference/action-result.md +0 -37
  128. data/lib/action/attachable/base.rb +0 -43
  129. data/lib/action/attachable/steps.rb +0 -63
  130. data/lib/action/attachable/subactions.rb +0 -70
  131. data/lib/action/attachable.rb +0 -17
  132. data/lib/action/configuration.rb +0 -55
  133. data/lib/action/core/automatic_logging.rb +0 -93
  134. data/lib/action/core/context/facade.rb +0 -48
  135. data/lib/action/core/flow/handlers/invoker.rb +0 -73
  136. data/lib/action/core/flow/handlers.rb +0 -20
  137. data/lib/action/core/logging.rb +0 -37
  138. data/lib/action/core/tracing.rb +0 -17
  139. data/lib/action/core/use_strategy.rb +0 -30
  140. data/lib/action/core/validation/validators/model_validator.rb +0 -34
  141. data/lib/action/core/validation/validators/type_validator.rb +0 -30
  142. data/lib/action/enqueueable/via_sidekiq.rb +0 -76
  143. data/lib/action/enqueueable.rb +0 -13
  144. data/lib/action/strategies/transaction.rb +0 -19
  145. data/lib/action/strategies.rb +0 -48
  146. data/lib/axn/util.rb +0 -24
  147. data/package.json +0 -10
  148. data/yarn.lock +0 -1166
@@ -0,0 +1,235 @@
1
+ # Form Strategy
2
+
3
+ The `form` strategy provides a declarative way to validate user input using form objects. It bridges the gap between raw user input (like `params`) and validated, structured data.
4
+
5
+ ::: tip When to Use
6
+ Use the form strategy when you need to validate **user-facing input** with user-friendly error messages. This is different from `expects` validations, which validate the **developer contract** (how the action is called).
7
+ :::
8
+
9
+ ## Basic Usage
10
+
11
+ ```ruby
12
+ class CreateUser
13
+ include Axn
14
+
15
+ use :form, type: CreateUser::Form
16
+
17
+ def call
18
+ # form is automatically validated and exposed
19
+ # If validation fails, the action fails with form.errors
20
+ User.create!(form.to_h)
21
+ end
22
+ end
23
+
24
+ class CreateUser::Form < Axn::FormObject
25
+ validates :email, presence: true, format: { with: URI::MailTo::EMAIL_REGEXP }
26
+ validates :name, presence: true, length: { minimum: 2 }
27
+ end
28
+ ```
29
+
30
+ ## Configuration Options
31
+
32
+ The form strategy accepts several configuration options:
33
+
34
+ | Option | Default | Description |
35
+ | ------ | ------- | ----------- |
36
+ | `type` | Auto-detected | The form class to use (see [Type Resolution](#type-resolution)) |
37
+ | `expect` | `:params` | The input field name to read from |
38
+ | `expose` | `:form` | The field name to expose the form object as |
39
+ | `inject` | `nil` | Additional context fields to inject into the form |
40
+
41
+ ### Type Resolution
42
+
43
+ The `type` option determines which form class to use:
44
+
45
+ 1. **Explicit class**: `use :form, type: MyFormClass`
46
+ 2. **String constant path**: `use :form, type: "CreateUser::Form"`
47
+ 3. **Auto-detected**: If not specified, inferred from action name + expose name (e.g., `CreateUser` + `:form` → `CreateUser::Form`)
48
+
49
+ ```ruby
50
+ # Explicit type
51
+ use :form, type: RegistrationForm
52
+
53
+ # String constant (useful for avoiding load order issues)
54
+ use :form, type: "Users::RegistrationForm"
55
+
56
+ # Auto-detected from action name
57
+ class CreateUser
58
+ include Axn
59
+ use :form # Uses CreateUser::Form
60
+ end
61
+ ```
62
+
63
+ ### Inline Form Definition
64
+
65
+ You can define the form class inline using a block:
66
+
67
+ ```ruby
68
+ class CreateUser
69
+ include Axn
70
+
71
+ use :form do
72
+ validates :email, presence: true
73
+ validates :name, presence: true
74
+ end
75
+
76
+ def call
77
+ User.create!(form.to_h)
78
+ end
79
+ end
80
+ ```
81
+
82
+ This creates an anonymous form class that inherits from `Axn::FormObject`.
83
+
84
+ ### Custom Field Names
85
+
86
+ ```ruby
87
+ class ProcessOrder
88
+ include Axn
89
+
90
+ # Read from :order_params, expose as :order_form
91
+ use :form, expect: :order_params, expose: :order_form, type: OrderForm
92
+
93
+ def call
94
+ # Access via order_form instead of form
95
+ Order.create!(order_form.to_h)
96
+ end
97
+ end
98
+ ```
99
+
100
+ ### Injecting Context
101
+
102
+ Use `inject` to pass additional context fields to the form:
103
+
104
+ ```ruby
105
+ class UpdateProfile
106
+ include Axn
107
+
108
+ expects :user, model: User
109
+ use :form, type: ProfileForm, inject: [:user]
110
+
111
+ def call
112
+ user.update!(form.to_h)
113
+ end
114
+ end
115
+
116
+ class ProfileForm < Axn::FormObject
117
+ attr_accessor :user # Injected from action context
118
+
119
+ validates :email, presence: true
120
+ validate :email_unique_for_other_users
121
+
122
+ private
123
+
124
+ def email_unique_for_other_users
125
+ return if user.nil?
126
+ return unless User.where.not(id: user.id).exists?(email: email)
127
+
128
+ errors.add(:email, "is already taken")
129
+ end
130
+ end
131
+ ```
132
+
133
+ ## How It Works
134
+
135
+ When you use the form strategy, the following happens automatically:
136
+
137
+ 1. **Expects params**: Adds `expects :params, type: :params` (or your custom `expect` field)
138
+ 2. **Exposes form**: Adds `exposes :form` (or your custom `expose` field)
139
+ 3. **Creates form**: Defines a memoized method that creates the form from params
140
+ 4. **Validates in before hook**: Runs `form.valid?` in a before hook; if invalid, the action fails
141
+
142
+ ```ruby
143
+ # This:
144
+ use :form, type: MyForm
145
+
146
+ # Is roughly equivalent to:
147
+ expects :params, type: :params
148
+ exposes :form, type: MyForm
149
+
150
+ def form
151
+ @form ||= MyForm.new(params)
152
+ end
153
+
154
+ before do
155
+ expose form: form
156
+ fail! unless form.valid?
157
+ end
158
+ ```
159
+
160
+ ## Error Handling
161
+
162
+ When form validation fails:
163
+ - The action fails (returns `ok? == false`)
164
+ - `result.error` contains a generic message
165
+ - `result.form.errors` contains the detailed validation errors
166
+
167
+ ```ruby
168
+ result = CreateUser.call(params: { email: "", name: "" })
169
+
170
+ result.ok? # => false
171
+ result.form.errors.full_messages
172
+ # => ["Email can't be blank", "Name can't be blank"]
173
+ ```
174
+
175
+ ### User-Facing Errors
176
+
177
+ To expose user-friendly error messages, configure a custom error handler:
178
+
179
+ ```ruby
180
+ class CreateUser
181
+ include Axn
182
+
183
+ use :form, type: CreateUser::Form
184
+
185
+ error { form.errors.full_messages.to_sentence }
186
+
187
+ def call
188
+ User.create!(form.to_h)
189
+ end
190
+ end
191
+ ```
192
+
193
+ ## Complete Example
194
+
195
+ ```ruby
196
+ class CreateCompanyMember
197
+ include Axn
198
+
199
+ expects :company, model: Company
200
+ use :form, type: MemberForm, inject: [:company]
201
+
202
+ exposes :member
203
+
204
+ error { form.errors.full_messages.to_sentence }
205
+ success { "#{member.name} has been added to #{company.name}" }
206
+
207
+ def call
208
+ member = company.members.create!(form.to_h)
209
+ expose member: member
210
+ end
211
+ end
212
+
213
+ class MemberForm < Axn::FormObject
214
+ attr_accessor :company # Injected
215
+
216
+ validates :email, presence: true, format: { with: URI::MailTo::EMAIL_REGEXP }
217
+ validates :name, presence: true
218
+ validates :role, presence: true, inclusion: { in: %w[admin member guest] }
219
+
220
+ validate :email_not_already_member
221
+
222
+ private
223
+
224
+ def email_not_already_member
225
+ return unless company&.members&.exists?(email: email)
226
+
227
+ errors.add(:email, "is already a member of this company")
228
+ end
229
+ end
230
+ ```
231
+
232
+ ## See Also
233
+
234
+ - [Axn::FormObject](/reference/form-object) - The base class for form objects
235
+ - [Validating User Input](/recipes/validating-user-input) - When to use form validation vs expects validation
@@ -19,7 +19,7 @@ To use a strategy in your action, call the `use` method with the strategy name:
19
19
 
20
20
  ```ruby
21
21
  class CreateUser
22
- include Action
22
+ include Axn
23
23
 
24
24
  use :transaction
25
25
 
@@ -35,11 +35,11 @@ end
35
35
 
36
36
  ### Using Strategies with Configuration
37
37
 
38
- Some strategies support configuration options. These strategies have a `setup` method that accepts configuration and returns a configured module. As an _imaginary_ example:
38
+ Some strategies support configuration options. These strategies have a `configure` method that accepts configuration and returns a configured module. As an _imaginary_ example:
39
39
 
40
40
  ```ruby
41
41
  class ProcessPayment
42
- include Action
42
+ include Axn
43
43
 
44
44
  use :retry, max_attempts: 3, backoff: :exponential
45
45
 
@@ -55,7 +55,7 @@ end
55
55
 
56
56
  ## Built-in Strategies
57
57
 
58
- The list of built in strategies is available via `Action::Strategies.built_in`.
58
+ The list of built in strategies is available via `Axn::Strategies.built_in`.
59
59
 
60
60
  ## Registering Custom Strategies
61
61
 
@@ -79,14 +79,14 @@ end
79
79
  Then register it with the strategies system:
80
80
 
81
81
  ```ruby
82
- Action::Strategies.register(:my_custom, MyCustomStrategy)
82
+ Axn::Strategies.register(:my_custom, MyCustomStrategy)
83
83
  ```
84
84
 
85
85
  Now you can use it in your actions:
86
86
 
87
87
  ```ruby
88
88
  class MyAction
89
- include Action
89
+ include Axn
90
90
 
91
91
  use :my_custom
92
92
 
@@ -98,13 +98,13 @@ end
98
98
 
99
99
  ### Configurable Strategies
100
100
 
101
- For strategies that need configuration, implement a `setup` method that returns a configured module:
101
+ For strategies that need configuration, implement a `configure` method that returns a configured module:
102
102
 
103
103
  ```ruby
104
104
  module RetryStrategy
105
105
  extend ActiveSupport::Concern
106
106
 
107
- def self.setup(max_attempts: 3, backoff: :linear, &block)
107
+ def self.configure(max_attempts: 3, backoff: :linear, &block)
108
108
  Module.new do
109
109
  extend ActiveSupport::Concern
110
110
 
@@ -142,15 +142,15 @@ module RetryStrategy
142
142
  end
143
143
 
144
144
  # Register the strategy
145
- Action::Strategies.register(:retry, RetryStrategy)
145
+ Axn::Strategies.register(:retry, RetryStrategy)
146
146
  ```
147
147
 
148
148
  ### Strategy Registration Best Practices
149
149
 
150
150
  1. **Register early**: Register custom strategies during application initialization
151
151
  2. **Use descriptive names**: Choose strategy names that clearly indicate their purpose
152
- 3. **Handle configuration validation**: Validate configuration options in your `setup` method
153
- 4. **Return proper modules**: Always return a module from the `setup` method
152
+ 3. **Handle configuration validation**: Validate configuration options in your `configure` method
153
+ 4. **Return proper modules**: Always return a module from the `configure` method
154
154
  5. **Document your strategies**: Include clear documentation for how to use your custom strategies
155
155
 
156
156
  ### Example: Complete Custom Strategy
@@ -161,7 +161,7 @@ Here's a complete example of a custom strategy that adds performance monitoring
161
161
  module PerformanceMonitoringStrategy
162
162
  extend ActiveSupport::Concern
163
163
 
164
- def self.setup(threshold_ms: 1000, notify_slow: false, &block)
164
+ def self.configure(threshold_ms: 1000, notify_slow: false, &block)
165
165
  Module.new do
166
166
  extend ActiveSupport::Concern
167
167
 
@@ -194,11 +194,11 @@ module PerformanceMonitoringStrategy
194
194
  end
195
195
 
196
196
  # Register the strategy
197
- Action::Strategies.register(:performance_monitoring, PerformanceMonitoringStrategy)
197
+ Axn::Strategies.register(:performance_monitoring, PerformanceMonitoringStrategy)
198
198
 
199
199
  # Use it in an action
200
200
  class ExpensiveCalculation
201
- include Action
201
+ include Axn
202
202
 
203
203
  use :performance_monitoring, threshold_ms: 500, notify_slow: true
204
204
 
@@ -227,7 +227,7 @@ end
227
227
  You can inspect all registered strategies:
228
228
 
229
229
  ```ruby
230
- Action::Strategies.all
230
+ Axn::Strategies.all
231
231
  # Returns a hash of strategy names to their modules
232
232
  ```
233
233
 
@@ -236,11 +236,11 @@ Action::Strategies.all
236
236
  To find a specific strategy by name:
237
237
 
238
238
  ```ruby
239
- Action::Strategies.find(:transaction)
239
+ Axn::Strategies.find(:transaction)
240
240
  # Returns the strategy module for the transaction strategy
241
241
 
242
- Action::Strategies.find(:nonexistent)
243
- # Raises Action::StrategyNotFound: Strategy 'nonexistent' not found
242
+ Axn::Strategies.find(:nonexistent)
243
+ # Raises Axn::StrategyNotFound: Strategy 'nonexistent' not found
244
244
  ```
245
245
 
246
246
  The `find` method is useful when you need to programmatically access a strategy module or verify that a strategy exists before using it.
@@ -250,15 +250,15 @@ The `find` method is useful when you need to programmatically access a strategy
250
250
  To reset strategies to only built-in ones (useful in tests):
251
251
 
252
252
  ```ruby
253
- Action::Strategies.clear!
253
+ Axn::Strategies.clear!
254
254
  ```
255
255
 
256
256
  ### Strategy Errors
257
257
 
258
258
  The following errors may be raised when using strategies:
259
259
 
260
- - `Action::StrategyNotFound`: When trying to use a strategy that hasn't been registered
261
- - `Action::DuplicateStrategyError`: When trying to register a strategy with a name that's already taken
260
+ - `Axn::StrategyNotFound`: When trying to use a strategy that hasn't been registered
261
+ - `Axn::DuplicateStrategyError`: When trying to register a strategy with a name that's already taken
262
262
  - `ArgumentError`: When providing configuration to a strategy that doesn't support it
263
263
 
264
264
  ## Best Practices
@@ -4,7 +4,7 @@ The `transaction` strategy wraps your action execution in a database transaction
4
4
 
5
5
  ```ruby
6
6
  class TransferFunds
7
- include Action
7
+ include Axn
8
8
 
9
9
  use :transaction
10
10
 
data/docs/usage/setup.md CHANGED
@@ -5,7 +5,7 @@ outline: deep
5
5
 
6
6
  ## Installation
7
7
 
8
- Adding `axn` to your Gemfile is enough to start using `include Action`.
8
+ Adding `axn` to your Gemfile is enough to start using `include Axn`.
9
9
 
10
10
  ## Global Configuration
11
11
 
@@ -19,7 +19,21 @@ By default any swallowed errors are noted in the logs, but it's _highly recommen
19
19
 
20
20
  ### Metrics / Tracing
21
21
 
22
- If you're using an APM provider, observability can be greatly enhanced by [configuring tracing and metrics hooks](/reference/configuration#tracing-and-metrics).
22
+ If you're using an APM provider, observability can be greatly enhanced by [configuring OpenTelemetry tracing and metrics hooks](/reference/configuration#opentelemetry-tracing). Axn automatically creates OpenTelemetry spans when OpenTelemetry is available.
23
+
24
+ ### Rails Integration (Optional)
25
+
26
+ When using Axn in a Rails application, you can configure how actions are autoloaded from the `app/actions` directory. By default, actions are loaded without any namespace, but you can configure a namespace to help differentiate them from existing service objects:
27
+
28
+ ```ruby
29
+ # config/initializers/axn.rb
30
+ Axn.configure do |c|
31
+ # Use :Actions namespace to differentiate from existing service objects
32
+ c.rails.app_actions_autoload_namespace = :Actions
33
+ end
34
+ ```
35
+
36
+ This is particularly useful when migrating from existing service object patterns, as it makes it easy to distinguish between new Axn actions and legacy service objects when you see `action.call` in your codebase.
23
37
 
24
38
  ### Code Quality (Optional)
25
39
 
data/docs/usage/steps.md CHANGED
@@ -31,7 +31,7 @@ The `step` method allows you to define steps inline with blocks:
31
31
 
32
32
  ```ruby
33
33
  class UserRegistration
34
- include Action
34
+ include Axn
35
35
  expects :email, :password, :name
36
36
  exposes :user_id, :welcome_message
37
37
 
@@ -66,7 +66,7 @@ The `steps` method allows you to compose existing action classes:
66
66
 
67
67
  ```ruby
68
68
  class ValidateInput
69
- include Action
69
+ include Axn
70
70
  expects :email, :password, :name
71
71
  exposes :validated_data
72
72
 
@@ -80,7 +80,7 @@ class ValidateInput
80
80
  end
81
81
 
82
82
  class CreateUser
83
- include Action
83
+ include Axn
84
84
  expects :validated_data
85
85
  exposes :user_id
86
86
 
@@ -91,7 +91,7 @@ class CreateUser
91
91
  end
92
92
 
93
93
  class SendWelcome
94
- include Action
94
+ include Axn
95
95
  expects :user_id, :validated_data
96
96
  exposes :welcome_message
97
97
 
@@ -102,7 +102,7 @@ class SendWelcome
102
102
  end
103
103
 
104
104
  class UserRegistration
105
- include Action
105
+ include Axn
106
106
  expects :email, :password, :name
107
107
  exposes :user_id, :welcome_message
108
108
 
@@ -117,7 +117,7 @@ You can combine both approaches:
117
117
 
118
118
  ```ruby
119
119
  class UserRegistration
120
- include Action
120
+ include Axn
121
121
  expects :email, :password, :name
122
122
  exposes :user_id, :welcome_message
123
123
 
@@ -292,7 +292,7 @@ end
292
292
 
293
293
  ```ruby
294
294
  class ProcessAPIRequest
295
- include Action
295
+ include Axn
296
296
  expects :request_data
297
297
  exposes :response_data
298
298
 
data/docs/usage/using.md CHANGED
@@ -7,9 +7,9 @@ outline: deep
7
7
 
8
8
  ## Common Case
9
9
 
10
- An action executed via `#call` _always_ returns an instance of the `Action::Result` class.
10
+ An action executed via `#call` _always_ returns an instance of the `Axn::Result` class.
11
11
 
12
- This means the result _always_ implements a consistent interface, including `ok?` and `error` (see [full details](/reference/action-result)) as well as any variables that the action `exposes`.
12
+ This means the result _always_ implements a consistent interface, including `ok?` and `error` (see [full details](/reference/axn-result)) as well as any variables that the action `exposes`.
13
13
 
14
14
  As a consumer, you usually want a conditional that surfaces `error` unless the result is `ok?` (remember that any exceptions have been swallowed), and otherwise takes whatever success action is relevant.
15
15
 
@@ -38,20 +38,31 @@ end
38
38
 
39
39
  ### `#call!`
40
40
 
41
- An action executed via `#call!` (note the `!`) does _not_ swallow exceptions -- a _successful_ action will return an `Action::Result` just like `call`, but any exceptions will bubble up uncaught (note: technically they _will_ be caught, your on_exception handler triggered, and then re-raised) and any explicit `fail!` calls will raise an `Action::Failure` exception with your custom message.
41
+ An action executed via `#call!` (note the `!`) does _not_ swallow exceptions -- a _successful_ action will return an `Axn::Result` just like `call`, but any exceptions will bubble up uncaught (note: technically they _will_ be caught, your on_exception handler triggered, and then re-raised) and any explicit `fail!` calls will raise an `Axn::Failure` exception with your custom message.
42
42
 
43
43
  This is a much less common pattern, as you're giving up the benefits of error swallowing and the consistent return interface guarantee, but it can be useful in limited contexts (usually for smaller, one-off scripts where it's easier to just let a failure bubble up rather than worry about adding conditionals for error handling).
44
44
 
45
45
 
46
- ### `#enqueue`
46
+ ### `#call_async`
47
47
 
48
- Before adopting this library, our code was littered with one-line workers whose only job was to fire off a service on a background job. We were able to remove that entire glue layer by directly supporting enqueueing sidekiq jobs from the Action itself.
48
+ Before adopting this library, our code was littered with one-line workers whose only job was to fire off a service on a background job. We were able to remove that entire glue layer by directly supporting async execution via background jobs from the Axn itself.
49
49
 
50
- ::: danger ALPHA
51
- Sidekiq integration is NOT YET TESTED/NOT YET USED IN OUR APP, and naming will VERY LIKELY change to make it clearer which actions will be retried!
52
- :::
50
+ ```ruby
51
+ class ProcessDataAction
52
+ include Axn
53
+
54
+ expects :data
55
+
56
+ def call
57
+ # Process data logic here
58
+ end
59
+ end
60
+
61
+ # Execute synchronously
62
+ result = ProcessDataAction.call(data: large_dataset)
63
+
64
+ # Execute asynchronously
65
+ ProcessDataAction.call_async(data: large_dataset)
66
+ ```
53
67
 
54
- * enqueue vs enqueue!
55
- * enqueue will not retry even if fails
56
- * enqueue! will go through normal sidekiq retries on any failure (including user-facing `fail!`)
57
- * Note implicit GlobalID support (if not serializable, will get ArgumentError at callsite)
68
+ For detailed information about configuring async adapters (Sidekiq, ActiveJob, etc.), see the [Async Execution documentation](/reference/async).