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,476 @@
1
+ ---
2
+ outline: deep
3
+ ---
4
+
5
+ # Mountable Actions
6
+
7
+ The mountable functionality is an advanced feature that allows you to mount actions directly to classes, providing convenient access patterns and reducing boilerplate. This is particularly useful for API clients to automatically wrap bits of logic in full Axn affordances, and for creating batch enqueueing methods that can process multiple items and enqueue them as individual background jobs.
8
+
9
+ ::: danger ALPHA
10
+ This is in VERY EXPERIMENTAL use at Teamshares, but the API is still definitely in flux.
11
+ :::
12
+
13
+ ## Overview
14
+
15
+ When you attach an action to a class, you get multiple ways to access it:
16
+
17
+ 1. **Direct method calls** on the class (e.g., `SomeClass.foo`), which depend on how you told it to mount
18
+ 3. **Namespace method calls** (e.g., `SomeClass::Axns.foo`) which always call the underlying axn directly (i.e. returning Axn::Result like a normal SomeAxn.call)
19
+
20
+ ## Attachment Strategies
21
+
22
+ ### `axn` Strategy
23
+
24
+ The `axn` strategy attaches an action that returns an `Axn::Result` object.
25
+
26
+ ```ruby
27
+ class UserService
28
+ include Axn
29
+
30
+ mount_axn(:create_user) do |email:, name:|
31
+ user = User.create!(email: email, name: name)
32
+ expose :user_id, user.id
33
+ end
34
+ end
35
+
36
+ # Usage
37
+ result = UserService.create_user(email: "user@example.com", name: "John")
38
+ if result.ok?
39
+ puts "User created with ID: #{result.user_id}"
40
+ else
41
+ puts "Error: #{result.error}"
42
+ end
43
+ ```
44
+
45
+ **Mounted methods:**
46
+ - `UserService.create_user(**kwargs)` - Returns `Axn::Result`
47
+ - `UserService.create_user!(**kwargs)` - Returns `Axn::Result` on success, raises on error
48
+ - `UserService.create_user_async(**kwargs)` - Executes asynchronously (requires async adapter configuration)
49
+
50
+ ### `mount_axn_method` Strategy
51
+
52
+ The `mount_axn_method` strategy creates methods that automatically extract the return value from the `Axn::Result`. This is a useful shorthand when you have a snippet that needs to return one or zero values, when you don't want to manually check if the result was ok?.
53
+
54
+ Note we only attach a bang version to be clear that on failure it'll raise an exception.
55
+
56
+ ```ruby
57
+ class Calculator
58
+ include Axn
59
+
60
+ mount_axn_method(:add) do |a:, b:|
61
+ a + b
62
+ end
63
+
64
+ mount_axn_method(:multiply) do |a:, b:|
65
+ a * b
66
+ end
67
+ end
68
+
69
+ # Usage
70
+ sum = Calculator.add!(a: 5, b: 3) # Returns 8 directly
71
+ product = Calculator.multiply!(a: 4, b: 6) # Returns 24 directly
72
+
73
+ # NOTE: you can still access the underlying Axn on the <wrapping_class>::Axns namespace
74
+ result = Calculator::Axns.add(a: 5, b: 3) # Returns Axn::Result
75
+ ```
76
+
77
+ **Mounted methods:**
78
+ - `Calculator.add!(**kwargs)` - Returns the extracted value directly, raises on error
79
+ - `Calculator::Axns.add(**kwargs)` - Returns `Axn::Result`
80
+
81
+ ### `step` Strategy
82
+
83
+ The `step` strategy is designed for composing actions into sequential workflows. Steps are executed as part of a larger action flow.
84
+
85
+ ```ruby
86
+ class OrderProcessor
87
+ include Axn
88
+ expects :order_data
89
+ exposes :order_id, :confirmation_number
90
+
91
+ step :validate_order, expects: [:order_data], exposes: [:validated_data] do
92
+ fail! "Invalid order data" if order_data[:items].empty?
93
+ expose :validated_data, order_data
94
+ end
95
+
96
+ step :create_order, expects: [:validated_data], exposes: [:order_id] do
97
+ order = Order.create!(validated_data)
98
+ expose :order_id, order.id
99
+ end
100
+
101
+ step :send_confirmation, expects: [:order_id], exposes: [:confirmation_number] do
102
+ confirmation = ConfirmationMailer.send_order_confirmation(order_id).deliver_now
103
+ expose :confirmation_number, confirmation.number
104
+ end
105
+
106
+ # call is automatically defined -- will execute steps in sequence
107
+ end
108
+
109
+ # Usage
110
+ result = OrderProcessor.call(order_data: { items: [...] })
111
+ if result.ok?
112
+ puts "Order #{result.order_id} created with confirmation #{result.confirmation_number}"
113
+ end
114
+ ```
115
+
116
+ **Available methods:**
117
+ - `OrderProcessor.call(**kwargs)` - Executes all steps in sequence
118
+
119
+ ## Async Execution
120
+
121
+ Mountable actions automatically support async execution when an async adapter is configured. Each mounted action gets a `_async` method that executes the action in the background.
122
+
123
+ ### Configuring Async Adapters
124
+
125
+ ```ruby
126
+ class DataProcessor
127
+ include Axn
128
+
129
+ # Configure async adapter (e.g., Sidekiq, ActiveJob)
130
+ async :sidekiq
131
+
132
+ mount_axn(:process_data, async: :sidekiq) do |data:|
133
+ # Processing logic
134
+ expose :processed_count, data.count
135
+ end
136
+ end
137
+
138
+ # Usage
139
+ # Synchronous execution
140
+ result = DataProcessor.process_data(data: large_dataset)
141
+
142
+ # Asynchronous execution
143
+ DataProcessor.process_data_async(data: large_dataset)
144
+ ```
145
+
146
+ ### Available Async Methods
147
+
148
+ When you attach an action using the `axn` strategy, you automatically get:
149
+ - `ClassName.action_name(**kwargs)` - Synchronous execution
150
+ - `ClassName.action_name!(**kwargs)` - Synchronous execution, raises on error
151
+ - `ClassName.action_name_async(**kwargs)` - Asynchronous execution
152
+
153
+ The `_async` methods require an async adapter to be configured. See the [Async Execution documentation](/reference/async) for more details on available adapters and configuration options.
154
+
155
+ ## Advanced Options
156
+
157
+ ### Inheritance Behavior
158
+
159
+ Mounted actions inherit features from their target class in different ways depending on the mounting strategy. Each strategy has sensible defaults, but you can customize inheritance behavior using the `inherit` parameter.
160
+
161
+ #### Default Behavior
162
+
163
+ Each mounting strategy has a default inheritance mode that fits its typical use case:
164
+
165
+ - **`mount_axn` and `mount_axn_method`**: Use `:lifecycle` mode (inherits hooks, callbacks, messages, and async config, but not fields)
166
+ - **`step`**: Uses `:none` mode (completely independent to avoid conflicts)
167
+
168
+ ```ruby
169
+ class UserService
170
+ include Axn
171
+
172
+ before :log_start
173
+ on_success :track_success
174
+ error "Parent error occurred"
175
+ async :sidekiq
176
+
177
+ def log_start
178
+ puts "Starting..."
179
+ end
180
+
181
+ def track_success
182
+ puts "Success!"
183
+ end
184
+
185
+ # Inherits lifecycle (hooks, callbacks, messages, async) but not fields
186
+ mount_axn :create_user do
187
+ # Will run log_start before and track_success after
188
+ expose :user_id, 123
189
+ end
190
+
191
+ # Completely independent - no inheritance
192
+ step :validate_user do
193
+ # Will NOT run log_start or track_success
194
+ expose :valid, true
195
+ end
196
+ end
197
+ ```
198
+
199
+ #### Inheritance Profiles
200
+
201
+ You can control what gets inherited using predefined profiles:
202
+
203
+ ##### `:lifecycle` Profile
204
+
205
+ Inherits everything except fields. Use this when the mounted action should fully participate in the parent's execution lifecycle:
206
+
207
+ ```ruby
208
+ mount_axn :process, inherit: :lifecycle do
209
+ # Inherits: hooks, callbacks, messages, async config
210
+ # Does NOT inherit: fields
211
+ end
212
+ ```
213
+
214
+ **What's inherited:**
215
+ - ✅ Hooks (`before`, `after`, `around`)
216
+ - ✅ Callbacks (`on_success`, `on_failure`, `on_error`, `on_exception`)
217
+ - ✅ Messages (`success`, `error`)
218
+ - ✅ Async configuration (`async :sidekiq`, etc.)
219
+ - ❌ Fields (`expects`, `exposes`)
220
+
221
+ ##### `:async_only` Profile
222
+
223
+ Only inherits async configuration. Use this for utility methods that need async capability but nothing else:
224
+
225
+ ```ruby
226
+ mount_axn :background_task, inherit: :async_only do
227
+ # Only inherits async config
228
+ # Completely independent otherwise
229
+ end
230
+ ```
231
+
232
+ **What's inherited:**
233
+ - ✅ Async configuration
234
+ - ❌ Everything else
235
+
236
+ ##### `:none` Profile
237
+
238
+ Completely standalone with no inheritance. Use this when the mounted action should be fully independent:
239
+
240
+ ```ruby
241
+ step :independent_step, inherit: :none do
242
+ # Completely isolated from parent
243
+ end
244
+ ```
245
+
246
+ **What's inherited:**
247
+ - ❌ Nothing - completely independent
248
+
249
+ #### Granular Control
250
+
251
+ For advanced use cases, you can use a hash to specify exactly what should be inherited:
252
+
253
+ ```ruby
254
+ mount_axn :custom, inherit: {
255
+ fields: false,
256
+ hooks: true,
257
+ callbacks: false,
258
+ messages: true,
259
+ async: true
260
+ } do
261
+ # Custom inheritance: only hooks, messages, and async
262
+ end
263
+ ```
264
+
265
+ **Available options:**
266
+ - `fields` - Field declarations (`expects`, `exposes`)
267
+ - `hooks` - Execution hooks (`before`, `after`, `around`)
268
+ - `callbacks` - Result callbacks (`on_success`, `on_failure`, `on_error`, `on_exception`)
269
+ - `messages` - Success and error messages
270
+ - `async` - Async adapter configuration
271
+
272
+ ::: info Strategies Always Inherit
273
+ Strategies (like `use :transaction`) are always inherited as they're part of the class ancestry chain. This cannot be controlled via the `inherit` parameter.
274
+ :::
275
+
276
+ #### Practical Examples
277
+
278
+ **Example 1: Step that needs parent's error messages**
279
+
280
+ ```ruby
281
+ class DataProcessor
282
+ include Axn
283
+
284
+ error "Data processing failed"
285
+
286
+ # Inherit only error messages, nothing else
287
+ step :validate, inherit: { fields: false, messages: true } do
288
+ fail! "Invalid data" # Will use parent's error message format
289
+ end
290
+ end
291
+ ```
292
+
293
+ **Example 2: Mounted action with custom hooks but no callbacks**
294
+
295
+ ```ruby
296
+ class ApiClient
297
+ include Axn
298
+
299
+ before :authenticate
300
+ on_success :log_success
301
+
302
+ def authenticate
303
+ # Auth logic
304
+ end
305
+
306
+ # Inherit hooks but not callbacks
307
+ mount_axn :fetch_data, inherit: { hooks: true, callbacks: false } do
308
+ # Will run authenticate before
309
+ # Will NOT run log_success callback
310
+ end
311
+ end
312
+ ```
313
+
314
+ **Example 3: Override default for a step**
315
+
316
+ ```ruby
317
+ class Workflow
318
+ include Axn
319
+
320
+ before :setup
321
+
322
+ # Steps default to :none, but we can override to inherit lifecycle
323
+ step :special_step, inherit: :lifecycle do
324
+ # Will run setup hook (unusual for a step)
325
+ end
326
+ end
327
+ ```
328
+
329
+ ### Error Prefixing for Steps
330
+
331
+ Steps automatically prefix error messages with the step name:
332
+
333
+ ```ruby
334
+ step :validation, expects: [:input] do
335
+ fail! "Input is invalid"
336
+ end
337
+
338
+ # If this step fails, the error message becomes: "validation: Input is invalid"
339
+ ```
340
+
341
+ You can customize the error prefix:
342
+
343
+ ```ruby
344
+ step :validation, expects: [:input], error_prefix: "Custom: " do
345
+ fail! "Input is invalid"
346
+ end
347
+
348
+ # Error message becomes: "Custom: Input is invalid"
349
+ ```
350
+
351
+ ## Method Naming and Validation
352
+
353
+ ### Valid Method Names
354
+
355
+ Method names must be convertible to valid Ruby constant names:
356
+
357
+ ```ruby
358
+ # ✅ Valid names
359
+ mount_axn(:create_user) # Creates CreateUser constant
360
+ mount_axn(:process_payment) # Creates ProcessPayment constant
361
+ mount_axn(:send-email) # Creates SendEmail constant (parameterized)
362
+ mount_axn(:step_1) # Creates Step1 constant
363
+
364
+ # ❌ Invalid names
365
+ mount_axn(:create_user!) # Cannot contain method suffixes (!?=)
366
+ mount_axn(:123invalid) # Cannot start with number
367
+ ```
368
+
369
+ ### Special Character Handling
370
+
371
+ The system automatically handles special characters using `parameterize`:
372
+
373
+ ```ruby
374
+ mount_axn(:send-email) # Becomes SendEmail constant
375
+ mount_axn(:step 1) # Becomes Step1 constant
376
+ mount_axn(:user@domain) # Becomes UserDomain constant
377
+ ```
378
+
379
+ ## Best Practices
380
+
381
+ ### 1. Choose the Right Strategy
382
+
383
+ - **Use `mount_axn`** when you need full `Axn::Result` objects and error handling
384
+ - **Use `mount_axn_method`** when you want direct return values for simple operations
385
+ - **Use `step`** when composing complex workflows with multiple sequential operations
386
+ - **Use `enqueues_each`** (from `Axn::Async`) when you need to process multiple items and enqueue each as a separate background job
387
+
388
+ ### 2. Keep Actions Focused
389
+
390
+ ```ruby
391
+ # ✅ Good: Focused action
392
+ mount_axn(:send_welcome_email) do |user_id:|
393
+ WelcomeMailer.send_welcome(user_id).deliver_now
394
+ end
395
+
396
+ # ❌ Bad: Too many responsibilities - prefer a standalone class
397
+ mount_axn(:process_user) do |user_data:|
398
+ user = User.create!(user_data)
399
+ WelcomeMailer.send_welcome(user.id).deliver_now
400
+ Analytics.track_user_signup(user.id)
401
+ # ... more logic
402
+ end
403
+ ```
404
+
405
+ ### 3. Use Descriptive Names
406
+
407
+ ```ruby
408
+ # ✅ Good: Clear intent
409
+ mount_axn(:validate_email_format)
410
+ mount_axn_method(:calculate_tax)
411
+ step(:send_confirmation_email)
412
+
413
+ # ❌ Bad: Unclear purpose
414
+ mount_axn(:process)
415
+ mount_axn_method(:do_thing)
416
+ step(:step1)
417
+ ```
418
+
419
+
420
+
421
+ ## Common Patterns
422
+
423
+ ### Service Objects
424
+
425
+ ```ruby
426
+ class UserService
427
+ include Axn
428
+
429
+ mount_axn(:create) do |email:, name:|
430
+ user = User.create!(email: email, name: name)
431
+ expose :user_id, user.id
432
+ end
433
+
434
+ mount_axn_method(:find_by_email) do |email:|
435
+ User.find_by(email: email)
436
+ end
437
+ end
438
+
439
+ # Usage
440
+ result = UserService.create(email: "user@example.com", name: "John")
441
+ user = UserService.find_by_email!(email: "user@example.com")
442
+ ```
443
+
444
+
445
+ ### Workflow Composition
446
+
447
+ ```ruby
448
+ class OrderWorkflow
449
+ include Axn
450
+ expects :order_data
451
+ exposes :order_id, :confirmation_number
452
+
453
+ step :validate, expects: [:order_data], exposes: [:validated_data] do
454
+ # Validation logic
455
+ expose :validated_data, order_data
456
+ end
457
+
458
+ step :create_order, expects: [:validated_data], exposes: [:order_id] do
459
+ order = Order.create!(validated_data)
460
+ expose :order_id, order.id
461
+ end
462
+
463
+ step :send_confirmation, expects: [:order_id], exposes: [:confirmation_number] do
464
+ # Send confirmation logic
465
+ expose :confirmation_number, "CONF-123"
466
+ end
467
+
468
+ def call
469
+ # Steps execute automatically
470
+ end
471
+ end
472
+ ```
473
+
474
+ ### Batch Processing
475
+
476
+ See `enqueues_each` in the [Async documentation](../reference/async.md) for batch processing with background jobs.