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
@@ -1,46 +1,131 @@
1
- ### Adding memoization
1
+ # Memoization
2
2
 
3
- For a practical example of [the `additional_includes` configuration](/reference/configuration#additional-includes) in practice, consider adding new functionality to all Actions.
3
+ Axn has built-in memoization support via the `memo` helper. This caches the result of method calls, ensuring they're only computed once per action execution.
4
4
 
5
- For instance, at Teamshares we automatically add memoization support (via [memo_wise](https://github.com/panorama-ed/memo_wise)) to all Actions. But we didn't want to add another dependency to the core library, so we've implemented this by:
5
+ ## Basic Usage
6
6
 
7
+ The `memo` helper works out of the box for methods without arguments:
7
8
 
8
9
  ```ruby
9
- Action.configure do |c|
10
- c.additional_includes = [TS::Memoization]
10
+ class GenerateReport
11
+ include Axn
12
+
13
+ expects :company, model: Company
14
+ exposes :report
15
+
16
+ def call
17
+ expose report: {
18
+ total_revenue: total_revenue,
19
+ top_products: top_products.map(&:name),
20
+ # top_products is only queried once, even though it's called twice
21
+ product_count: top_products.count
22
+ }
23
+ end
24
+
25
+ private
26
+
27
+ memo def top_products
28
+ company.products.order(sales_count: :desc).limit(10)
29
+ end
30
+
31
+ memo def total_revenue
32
+ company.orders.sum(:total)
11
33
  end
34
+ end
12
35
  ```
13
36
 
37
+ ## How It Works
38
+
39
+ - `memo` wraps the method and caches its return value on first call
40
+ - Subsequent calls return the cached value without re-executing the method
41
+ - Memoization is scoped to the action instance, so each `call` starts fresh
42
+
43
+ ## Methods With Arguments
44
+
45
+ For methods that accept arguments, Axn supports the `memo_wise` gem:
46
+
47
+ ```ruby
48
+ # Gemfile
49
+ gem "memo_wise"
50
+ ```
51
+
52
+ With `memo_wise` available, you can automatically memoize methods with arguments:
53
+
14
54
  ```ruby
15
- module TS::Memoization
16
- extend ActiveSupport::Concern
55
+ class CalculatePricing
56
+ include Axn
17
57
 
18
- included do
19
- prepend MemoWise
58
+ expects :product
59
+ exposes :pricing
60
+
61
+ def call
62
+ expose pricing: {
63
+ retail: price_for(:retail),
64
+ wholesale: price_for(:wholesale),
65
+ # Each unique argument is cached separately
66
+ bulk: price_for(:bulk)
67
+ }
20
68
  end
21
69
 
22
- class_methods do
23
- def memo(...) = memo_wise(...)
70
+ private
71
+
72
+ memo def price_for(tier)
73
+ # Complex pricing calculation...
74
+ PricingEngine.calculate(product, tier:)
24
75
  end
25
76
  end
26
77
  ```
27
78
 
28
- And with those pieces in place `memo` is available in all Actions:
79
+ If you try to use `memo` on a method with arguments without `memo_wise` installed, you'll get a helpful error:
80
+
81
+ ```
82
+ ArgumentError: Memoization of methods with arguments requires the 'memo_wise' gem.
83
+ Please add 'memo_wise' to your Gemfile or use a method without arguments.
84
+ ```
85
+
86
+ ## When to Use Memoization
87
+
88
+ Memoization is particularly useful for:
89
+
90
+ - **Database queries** called multiple times within an action
91
+ - **API calls** or external service lookups
92
+ - **Complex computations** that are expensive to repeat
29
93
 
30
94
  ```ruby
31
- class ContrivedExample
32
- include Action
95
+ class SyncUserData
96
+ include Axn
33
97
 
34
- exposes :nums
98
+ expects :user, model: User
35
99
 
36
100
  def call
37
- expose nums: Array.new(10) { random_number }
101
+ update_profile if needs_profile_update?
102
+ update_preferences if needs_preferences_update?
103
+ notify_if_changed
38
104
  end
39
105
 
40
106
  private
41
107
 
42
- memo def random_number = rand(1..100) # [!code focus]
108
+ # Called multiple times - only fetches once
109
+ memo def external_data
110
+ ExternalApi.fetch_user_data(user.external_id)
111
+ end
112
+
113
+ def needs_profile_update?
114
+ external_data[:profile_version] > user.profile_version
115
+ end
116
+
117
+ def needs_preferences_update?
118
+ external_data[:preferences_hash] != user.preferences_hash
119
+ end
120
+
121
+ def notify_if_changed
122
+ # ...
123
+ end
43
124
  end
44
125
  ```
45
126
 
46
- Because of the `memo` usage, `ContrivedExample.call.nums` will be a ten-element array of _the same number_, rather than re-calling `rand` for each element.
127
+ ## Notes
128
+
129
+ - Memoization persists only for the duration of a single action execution
130
+ - When `memo_wise` is available, Axn automatically uses it (no configuration needed)
131
+ - See the [memo_wise documentation](https://github.com/panorama-ed/memo_wise) for advanced features like cache resetting
@@ -1,12 +1,16 @@
1
1
  # RuboCop Integration
2
2
 
3
- Axn provides custom RuboCop cops to help enforce best practices and maintain code quality in your Action-based codebase.
3
+ Axn provides a custom RuboCop cop to help enforce proper result handling when calling Actions.
4
4
 
5
- ## Overview
5
+ ## What It Does
6
6
 
7
- The `Axn/UncheckedResult` cop enforces proper result handling when calling Actions. It can detect when Action results are ignored and help ensure consistent error handling patterns.
7
+ The `Axn/UncheckedResult` cop detects when you call another Action from within an Action but don't properly handle the result. This helps prevent silent failures and ensures consistent error handling patterns.
8
8
 
9
- ## Installation
9
+ > **⚠️ Warning**: This cop uses static analysis and cannot distinguish between actual Axn classes and other classes that happen to have a `call` method. If you're using legacy services or other service patterns alongside Axn, you may encounter false positives. Use RuboCop disable comments for intentional violations.
10
+ >
11
+ > **💡 Tip**: If you're using the Actions namespace (see [Rails Integration](/usage/setup#rails-integration-optional)), you can configure the cop to only check `Actions::*` classes, eliminating false positives from other service objects.
12
+
13
+ ## Setup
10
14
 
11
15
  ### 1. Add to Your .rubocop.yml
12
16
 
@@ -14,137 +18,37 @@ The `Axn/UncheckedResult` cop enforces proper result handling when calling Actio
14
18
  require:
15
19
  - axn/rubocop
16
20
 
17
- # Enable Axn's custom cop
18
21
  Axn/UncheckedResult:
19
22
  Enabled: true
20
- CheckNested: true # Check nested Action calls
21
- CheckNonNested: true # Check non-nested Action calls
22
- Severity: warning # or error
23
+ Severity: warning
23
24
  ```
24
25
 
25
26
  ### 2. Verify Installation
26
27
 
27
- Run RuboCop to ensure the cop is loaded:
28
-
29
28
  ```bash
30
29
  bundle exec rubocop --show-cops | grep Axn
31
30
  ```
32
31
 
33
- You should see:
34
- ```
35
- Axn/UncheckedResult
36
- ```
37
-
38
- ## Configuration Options
39
-
40
- ### CheckNested
41
-
42
- Controls whether the cop checks Action calls that are inside other Action classes.
43
-
44
- ```yaml
45
- Axn/UncheckedResult:
46
- CheckNested: true # Check nested calls (default)
47
- CheckNested: false # Skip nested calls
48
- ```
49
-
50
- **When to use `CheckNested: false`:**
51
- - You're gradually adopting the rule and want to focus on top-level calls first
52
- - Your team has different standards for nested vs. non-nested calls
53
- - You're using a different pattern for nested Action handling
54
-
55
- ### CheckNonNested
56
-
57
- Controls whether the cop checks Action calls that are outside Action classes.
58
-
59
- ```yaml
60
- Axn/UncheckedResult:
61
- CheckNonNested: true # Check non-nested calls (default)
62
- CheckNonNested: false # Skip non-nested calls
63
- ```
64
-
65
- **When to use `CheckNonNested: false`:**
66
- - You're only concerned about nested Action calls
67
- - Top-level Action calls are handled by other tools or processes
68
- - You want to focus on the most critical use case first
69
-
70
- ### Severity
71
-
72
- Controls how violations are reported.
73
-
74
- ```yaml
75
- Axn/UncheckedResult:
76
- Severity: warning # Show as warnings (default)
77
- Severity: error # Show as errors (fails CI)
78
- ```
79
-
80
- ## Common Configuration Patterns
32
+ You should see `Axn/UncheckedResult` in the output.
81
33
 
82
- ### Full Enforcement (Recommended for New Projects)
34
+ ## Basic Usage
83
35
 
84
- ```yaml
85
- Axn/UncheckedResult:
86
- Enabled: true
87
- CheckNested: true
88
- CheckNonNested: true
89
- Severity: error
90
- ```
91
-
92
- ### Gradual Adoption (Recommended for Existing Projects)
93
-
94
- ```yaml
95
- Axn/UncheckedResult:
96
- Enabled: true
97
- CheckNested: true # Start with nested calls
98
- CheckNonNested: false # Add this later
99
- Severity: warning # Start with warnings
100
- ```
101
-
102
- ### Nested-Only Focus
103
-
104
- ```yaml
105
- Axn/UncheckedResult:
106
- Enabled: true
107
- CheckNested: true
108
- CheckNonNested: false
109
- Severity: warning
110
- ```
111
-
112
- ## What the Cop Checks
113
-
114
- The cop analyzes your code to determine if you're:
115
-
116
- 1. **Inside an Action class** - Classes that `include Action`
117
- 2. **Inside the `call` method** - Only the main execution method
118
- 3. **Calling another Action** - Using `.call` on Action classes
119
- 4. **Properly handling the result** - One of the acceptable patterns
120
-
121
- ## What the Cop Ignores
122
-
123
- The cop will NOT report offenses for:
124
-
125
- - Action calls outside of Action classes (if `CheckNonNested: false`)
126
- - Action calls in methods other than `call`
127
- - Action calls that use `call!` (bang method)
128
- - Action calls where the result is properly handled
129
-
130
- ## Proper Result Handling Patterns
131
-
132
- ### ✅ Using call!
36
+ ### ✅ Good - Using call!
133
37
 
134
38
  ```ruby
135
39
  class OuterAction
136
- include Action
40
+ include Axn
137
41
  def call
138
42
  InnerAction.call!(param: "value") # Exceptions bubble up
139
43
  end
140
44
  end
141
45
  ```
142
46
 
143
- ### ✅ Checking result.ok?
47
+ ### ✅ Good - Checking the result
144
48
 
145
49
  ```ruby
146
50
  class OuterAction
147
- include Action
51
+ include Axn
148
52
  def call
149
53
  result = InnerAction.call(param: "value")
150
54
  return result unless result.ok?
@@ -153,200 +57,50 @@ class OuterAction
153
57
  end
154
58
  ```
155
59
 
156
- ### Checking result.failed?
157
-
158
- ```ruby
159
- class OuterAction
160
- include Action
161
- def call
162
- result = InnerAction.call(param: "value")
163
- if result.failed?
164
- return result
165
- end
166
- # Process successful result...
167
- end
168
- end
169
- ```
170
-
171
- ### ✅ Accessing result.error
172
-
173
- ```ruby
174
- class OuterAction
175
- include Action
176
- def call
177
- result = InnerAction.call(param: "value")
178
- if result.error
179
- return result
180
- end
181
- # Process successful result...
182
- end
183
- end
184
- ```
185
-
186
- ### ✅ Returning the result
187
-
188
- ```ruby
189
- class OuterAction
190
- include Action
191
- def call
192
- result = InnerAction.call(param: "value")
193
- result # Result is returned, so it's properly handled
194
- end
195
- end
196
- ```
197
-
198
- ### ✅ Using result in expose
199
-
200
- ```ruby
201
- class OuterAction
202
- include Action
203
- exposes :nested_result
204
- def call
205
- result = InnerAction.call(param: "value")
206
- expose nested_result: result # Result is used, so it's properly handled
207
- end
208
- end
209
- ```
210
-
211
- ### ✅ Passing result to another method
212
-
213
- ```ruby
214
- class OuterAction
215
- include Action
216
- def call
217
- result = InnerAction.call(param: "value")
218
- process_result(result) # Result is used, so it's properly handled
219
- end
220
- end
221
- ```
222
-
223
- ## Common Anti-Patterns
224
-
225
- ### ❌ Ignoring the result
60
+ ### Bad - Ignoring the result
226
61
 
227
62
  ```ruby
228
63
  class OuterAction
229
- include Action
64
+ include Axn
230
65
  def call
231
- InnerAction.call(param: "value") # Result ignored - will trigger offense
66
+ InnerAction.call(param: "value") # Will trigger offense
232
67
  # This continues even if InnerAction fails
233
68
  end
234
69
  end
235
70
  ```
236
71
 
237
- ### ❌ Assigning but not using
72
+ ## Configuration
238
73
 
239
- ```ruby
240
- class OuterAction
241
- include Action
242
- def call
243
- result = InnerAction.call(param: "value") # Assigned but never used
244
- # Will trigger offense unless result is properly handled
245
- end
246
- end
247
- ```
74
+ The cop supports flexible configuration:
248
75
 
249
- ### ❌ Using unrelated attributes
250
-
251
- ```ruby
252
- class OuterAction
253
- include Action
254
- def call
255
- result = InnerAction.call(param: "value")
256
- some_other_method(result.some_other_attribute) # Not checking success/failure
257
- # Will trigger offense - need to check result.ok? first
258
- end
259
- end
76
+ ```yaml
77
+ Axn/UncheckedResult:
78
+ Enabled: true
79
+ CheckNested: true # Check nested Axn calls (default: true)
80
+ CheckNonNested: true # Check non-nested Axn calls (default: true)
81
+ ActionsNamespace: "Actions" # Only check Actions::* classes (optional)
82
+ Severity: warning # or error
260
83
  ```
261
84
 
262
- ## Migration Strategies
263
-
264
- ### For New Projects
265
-
266
- 1. Enable the cop with full enforcement from the start
267
- 2. Use `Severity: error` to catch violations early
268
- 3. Train your team on the proper patterns
269
-
270
- ### For Existing Projects
85
+ ### ActionsNamespace Configuration
271
86
 
272
- 1. **Phase 1**: Enable with `CheckNested: true, CheckNonNested: false, Severity: warning`
273
- 2. **Phase 2**: Fix all nested Action violations
274
- 3. **Phase 3**: Enable `CheckNonNested: true`
275
- 4. **Phase 4**: Fix all non-nested Action violations
276
- 5. **Phase 5**: Set `Severity: error`
87
+ When using the Actions namespace, you can configure the cop to only check calls on `Actions::*` classes:
277
88
 
278
- ### Using RuboCop Disable Comments
89
+ ```yaml
90
+ Axn/UncheckedResult:
91
+ ActionsNamespace: "Actions"
92
+ ```
279
93
 
280
- For intentional violations, you can disable the cop:
94
+ This eliminates false positives from other service objects while still catching unchecked Axn action calls:
281
95
 
282
96
  ```ruby
283
97
  class OuterAction
284
- include Action
98
+ include Axn
285
99
  def call
286
- # rubocop:disable Axn/UncheckedResult
287
- InnerAction.call(param: "value") # Intentionally ignored
288
- # rubocop:enable Axn/UncheckedResult
100
+ SomeService.call(param: "value") # Won't trigger cop
101
+ Actions::InnerAction.call(param: "value") # Will trigger cop
289
102
  end
290
103
  end
291
104
  ```
292
105
 
293
- ## Troubleshooting
294
-
295
- ### Cop Not Loading
296
-
297
- If you see "uninitialized constant" errors:
298
-
299
- 1. Ensure the gem is properly installed: `bundle list | grep axn`
300
- 2. Check your `.rubocop.yml` syntax
301
- 3. Verify the require path: `require: - axn/rubocop`
302
-
303
- ### False Positives
304
-
305
- If the cop reports violations for properly handled results:
306
-
307
- 1. Check that you're using the exact patterns shown above
308
- 2. Ensure the result variable name matches exactly
309
- 3. Verify the result is being used in an acceptable way
310
-
311
- ### Performance Issues
312
-
313
- The cop analyzes AST nodes, so it's generally fast. If you experience slowdowns:
314
-
315
- 1. Ensure you're not running RuboCop on very large files
316
- 2. Consider using RuboCop's `--parallel` option
317
- 3. Use `.rubocop_todo.yml` for gradual adoption
318
-
319
- ## Best Practices
320
-
321
- 1. **Start Small**: Begin with warnings and nested calls only
322
- 2. **Be Consistent**: Choose one pattern and stick with it
323
- 3. **Train Your Team**: Make sure everyone understands the rules
324
- 4. **Review Regularly**: Use the cop in your CI/CD pipeline
325
- 5. **Document Exceptions**: Use disable comments sparingly and document why
326
-
327
- ## Integration with CI/CD
328
-
329
- Add RuboCop to your CI pipeline to catch violations early:
330
-
331
- ```yaml
332
- # .github/workflows/rubocop.yml
333
- name: RuboCop
334
- on: [push, pull_request]
335
- jobs:
336
- rubocop:
337
- runs-on: ubuntu-latest
338
- steps:
339
- - uses: actions/checkout@v3
340
- - uses: ruby/setup-ruby@v1
341
- with:
342
- ruby-version: 3.2
343
- - run: bundle install
344
- - run: bundle exec rubocop
345
- ```
346
-
347
- ## Related Resources
348
-
349
- - [Action Result Reference](/reference/action-result)
350
- - [Configuration Guide](/reference/configuration)
351
- - [Testing Recipes](/recipes/testing)
352
- - [Best Practices Guide](/advanced/conventions)
106
+ For detailed configuration options, usage patterns, and troubleshooting, see the [technical documentation](https://github.com/teamshares/axn/blob/main/lib/rubocop/cop/axn/README.md).
@@ -8,23 +8,23 @@
8
8
 
9
9
  Say you're writing unit specs for PrimaryAction that calls Subaction, and you want to mock out the Subaction call.
10
10
 
11
- To generate a successful Action::Result:
11
+ To generate a successful Axn::Result:
12
12
 
13
- * Base case: `Action::Result.ok`
14
- * [Optional] Custom message: `Action::Result.ok("It went awesome")`
15
- * [Optional] Custom exposures: `Action::Result.ok("It went awesome", some_var: 123)`
13
+ * Base case: `Axn::Result.ok`
14
+ * [Optional] Custom message: `Axn::Result.ok("It went awesome")`
15
+ * [Optional] Custom exposures: `Axn::Result.ok("It went awesome", some_var: 123)`
16
16
 
17
- To generate a failed Action::Result:
17
+ To generate a failed Axn::Result:
18
18
 
19
- * Base case: `Action::Result.error`
20
- * [Optional] Custom message: `Action::Result.error("It went poorly")`
21
- * [Optional] Custom exposures: `Action::Result.error("It went poorly", some_var: 123)`
22
- * [Optional] Custom exception: `Action::Result.error(some_var: 123) { raise FooBarException.new("bad thing") }`
19
+ * Base case: `Axn::Result.error`
20
+ * [Optional] Custom message: `Axn::Result.error("It went poorly")`
21
+ * [Optional] Custom exposures: `Axn::Result.error("It went poorly", some_var: 123)`
22
+ * [Optional] Custom exception: `Axn::Result.error(some_var: 123) { raise FooBarException.new("bad thing") }`
23
23
 
24
24
  Either way, using those to mock an actual call would look something like this in your rspec:
25
25
 
26
26
  ```ruby
27
- let(:subaction_response) { Action::Result.ok("custom message", foo: 1) }
27
+ let(:subaction_response) { Axn::Result.ok("custom message", foo: 1) }
28
28
 
29
29
  before do
30
30
  expect(Subaction).to receive(:call).and_return(subaction_response)
@@ -38,7 +38,7 @@ The semantics of call-bang are a little different -- if Subaction is called via
38
38
  ### Success
39
39
 
40
40
  ```ruby
41
- let(:subaction_response) { Action::Result.ok("custom message", foo: 1) }
41
+ let(:subaction_response) { Axn::Result.ok("custom message", foo: 1) }
42
42
 
43
43
  before do
44
44
  expect(Subaction).to receive(:call!).and_return(subaction_response)
@@ -57,18 +57,18 @@ before do
57
57
  end
58
58
  ```
59
59
 
60
- NOTE: to mock subaction failing via explicit `fail!` call, you'd use an `Action::Failure` exception class.
60
+ NOTE: to mock subaction failing via explicit `fail!` call, you'd use an `Axn::Failure` exception class.
61
61
 
62
62
  ## Mocking Axn arguments
63
63
 
64
- Be aware that in order to improve testing ergonomics, the `type` validation will return `true` for _any_ `RSpec::Mocks::` subclass _as long as `Action.config.env.test?` is `true`_.
64
+ Be aware that in order to improve testing ergonomics, the `type` validation will return `true` for _any_ `RSpec::Mocks::` subclass _as long as `Axn.config.env.test?` is `true`_.
65
65
 
66
66
  This makes it much easier to test Axns, as you can pass in mocks without immediately failing the inbound validation.
67
67
 
68
68
  ```ruby
69
69
  subject(:result) { action.call!(sym:) }
70
70
 
71
- let(:action) { build_action { expects :sym, type: Symbol } }
71
+ let(:action) { build_axn { expects :sym, type: Symbol } }
72
72
 
73
73
  context "with a symbol" do
74
74
  let(:sym) { :hello }
@@ -1,7 +1,7 @@
1
1
  # Validating _user_ input
2
2
 
3
3
  ::: danger ALPHA
4
- This has not yet been fully fleshed out. For now, the general idea is that user-facing validation is a _separate layer_ from the declarative expectations about what inputs your Action takes (e.g. `expects :params` and pass to a form object, rather than accepting field-level params directly).
4
+ This has not yet been fully fleshed out. For now, the general idea is that user-facing validation is a _separate layer_ from the declarative expectations about what inputs your Axn takes (e.g. `expects :params` and pass to a form object, rather than accepting field-level params directly).
5
5
  :::
6
6
 
7
7