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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6e825e850ff02abfa953a14dac87254b9775affb063a64f7c0138d98eda4b99d
4
- data.tar.gz: de54b6c1a8274de2ec8131e1c3d2c53c7ea9eb844b0ec0661682f212dc00cda7
3
+ metadata.gz: 8930af546a87917b02eb7c4af3fdc88670f6b323cbf9e7223553eca07ac6bf8c
4
+ data.tar.gz: 47a70ea981ae8c9a7405e02c844174996c0d1a3b9788cbf5c37d725d77f00d5c
5
5
  SHA512:
6
- metadata.gz: b307adb81a4eac8b0cebca6778eeb060af5013a6b008f414085501e5cb7c8bb9fc32fbd4770ac8e458bf5a4f919d61b022bc5bc129199b3ae7b2f562f85f4b6a
7
- data.tar.gz: 692efe5f507fd04dec768862eb9277e2a39af3b8eea182b2e8f1ca09e0f0910ab7080dd07f6d7f5605a673cab0feab7aa1194032b54ad2c550e42b794bc44965
6
+ metadata.gz: 83c3a6d065d240bf73ccca409b40e2a0560901617d98b5fe3f9aaefbf3ef87ab444bdd20220b01ce8481ccea55278240433a0641d9549c63a47329d13e3bb5ce
7
+ data.tar.gz: adfe79828b95caa34379fe0424b4aa2b9a3ee06247eeb29b10c2b7b9fded83dcf327993e13da84f913e5e2c05ad0b53a77562ffe3c1ad25d814265c526b49a78
@@ -0,0 +1,36 @@
1
+ Create a pull request for the current branch.
2
+
3
+ ## Pre-flight checks
4
+
5
+ 1. Run `git status` to check for uncommitted changes.
6
+ If there are uncommitted changes, stop and ask the user to commit or stash them first
7
+ 2. Run `git branch --show-current` to get the branch name
8
+ Verify not on main/master - if so, stop and ask the user to create a branch first
9
+ 3. Run the test suite (`bundle exec rake all_specs` and `bundle exec rubocop`)
10
+ If any test suite failures, stop and prompt user to resolve first
11
+
12
+ ## Gather context
13
+
14
+ 4. Run `git log origin/main..HEAD --oneline` to see all commits on this branch
15
+ 5. Run `git diff origin/main...HEAD --stat` to see changed files
16
+ 6. If needed, read the diff for key files to understand the changes
17
+
18
+ ## Generate PR content
19
+
20
+ 7. Generate a PR title: a single line that summarizes the changes (imperative mood, e.g., "Add batch enqueueing support")
21
+ 8. Generate a PR body with:
22
+ - **Summary**: a handful of bullet points describing the key changes
23
+ - **Details**: If sufficiently complex, include details of each notable change
24
+ - **Usage Example**: If applicable, a brief code example demonstrating the new or changed feature/API
25
+
26
+ ## Push and create PR
27
+
28
+ 9. Push the branch: `git push -u origin HEAD`
29
+ 10. Create the PR as a draft using gh:
30
+
31
+ gh pr create --draft --title "THE TITLE" --body "$(cat <<'EOF'
32
+ THE BODY HERE
33
+ EOF
34
+ )"
35
+
36
+ 11. Report the PR URL to the user as a link they can click
@@ -0,0 +1,43 @@
1
+ # Axn Framework Usage Patterns
2
+
3
+ ## Basic Action Structure
4
+ - Always include `Axn` module: `class MyAction; include Axn; end`
5
+ - Declare interface with `expects` and `exposes` at class level
6
+ - Implement `call` method for action logic
7
+ - Use `expose` to return data (must be declared in `exposes` first)
8
+ - Use `fail!` for expected failures, `done!` for early success completion
9
+
10
+ ## Execution Patterns
11
+ - Use `call` for result objects (swallows exceptions, returns `Axn::Result`)
12
+ - Use `call!` for exception-throwing behavior (re-raises exceptions)
13
+ - Use `call_async` for background execution (requires async configuration)
14
+
15
+ ## Data Flow and Validation
16
+ - `expects` declares required input parameters with validation options
17
+ - `exposes` declares output data that must be provided via `expose`
18
+ - Common validation options: `type:`, `optional:`, `allow_nil:`, `allow_blank:`, `default:`, `sensitive:`
19
+ - Use `model: true` for auto-hydrating records from IDs
20
+ - Use `on:` for nested field expectations and accessors
21
+
22
+ ## Composition Patterns
23
+ - Use `step :name, expects: [:input], exposes: [:output]` for inline steps
24
+ - Use `steps(Class1, Class2)` to compose existing action classes
25
+ - Steps execute sequentially and share data through `expects`/`exposes`
26
+ - Step failures are automatically prefixed with step name
27
+
28
+ ## Message and Error Handling
29
+ - Use `success` and `error` declarations for custom messages
30
+ - Support conditional messages with `if:`/`unless:` matchers
31
+ - Use `from:` parameter for error message inheritance from child actions
32
+ - Use `prefix:` for consistent error message formatting
33
+ - Define static messages first, then conditional messages
34
+
35
+ ## Async and Background Processing
36
+ - Configure with `async :sidekiq` or `async :active_job`
37
+ - Use `call_async` for background execution
38
+ - Supports all adapter-specific configuration options
39
+
40
+ ## Hooks and Callbacks
41
+ - Hooks (`before`/`after`/`around`) execute as part of `call` - failures affect `result.ok?`
42
+ - Callbacks (`on_success`/`on_error`/`on_failure`/`on_exception`) execute after `call` - failures don't affect `result.ok?`
43
+ - Use `done!` for early completion (skips `after` hooks, allows `around` hooks to complete)
@@ -0,0 +1,27 @@
1
+ # General Coding Standards
2
+
3
+ ## Code Quality Principles
4
+ - Always prioritize clean, elegant, and maintainable code following these principles:
5
+ - Write code that is both functional and beautiful - elegance should be a primary concern
6
+ - Follow existing patterns in the codebase
7
+ - Use DRY principles and eliminate duplication
8
+ - Create reusable/shared examples for similar test patterns
9
+ - Consolidate similar functionality where appropriate
10
+ - Match existing code style and structure
11
+ - Consider maintainability and extensibility first
12
+ - Look for refactoring opportunities
13
+ - Use parameterized testing and shared examples in specs
14
+ - Prefer simple, readable solutions over clever but complex ones
15
+ - Write code that tells a story and is self-documenting
16
+
17
+ ## Method Organization
18
+ - Place private method declarations under a private stanza rather than marking them private after the fact
19
+ - Use single-line changelog edits
20
+ - Use Ruby endless method definitions for short helper methods
21
+ - Use a series of return-early if statements instead of long if/else chains for stylistic consistency
22
+
23
+ ## Communication Style
24
+ - Be direct, neutral, and concise
25
+ - Be sparing with praise, flattery, or enthusiasm
26
+ - Do not say the user is right unless verified; confirm first
27
+ - Prefer: "It appears/It seems/Based on X…" over absolute claims
@@ -0,0 +1,40 @@
1
+ # RSpec Testing Patterns
2
+
3
+ ## Use build_axn Spec Helper
4
+ - Always use the `build_axn` spec helper instead of manually creating Axn classes
5
+ - Prefer `build_axn { ... }` over `Axn::Factory.build { ... }` in test files
6
+ - Prefer `build_axn { ... }` over `Class.new.send(:include, Axn)` patterns
7
+ - The `build_axn` helper is already included via `Axn::Testing::SpecHelpers` and provides a cleaner, more readable way to create test Axn classes
8
+
9
+ ## DRY Test Setup
10
+ - When writing RSpec tests with repeated object creation patterns, prefer using `let`/`let!` to define base objects once and override specific attributes in contexts
11
+ - Use shared examples (`shared_examples`/`include_examples`) for testing similar methods with different parameters
12
+ - Avoid recreating the same object structure multiple times - use `let` to override only the values that change between contexts
13
+
14
+ ### Example Pattern:
15
+ ```ruby
16
+ # Instead of recreating objects in each context:
17
+ context "scenario 1" do
18
+ let!(:object) { create(:model, attr1: value1, attr2: value2) }
19
+ end
20
+ context "scenario 2" do
21
+ let!(:object) { create(:model, attr1: value3, attr2: value4) }
22
+ end
23
+
24
+ # Prefer this pattern:
25
+ let!(:object) { create(:model, attr1: attr1_value, attr2: attr2_value) }
26
+ context "scenario 1" do
27
+ let(:attr1_value) { value1 }
28
+ let(:attr2_value) { value2 }
29
+ end
30
+ context "scenario 2" do
31
+ let(:attr1_value) { value3 }
32
+ let(:attr2_value) { value4 }
33
+ end
34
+ ```
35
+
36
+ ## Test Organization
37
+ - Use `it { expect(...).to eq(...) }` shorthand for simple assertions. `it { is_expected.to ... }` even better.
38
+ - Group related tests with shared examples when testing similar methods
39
+ - Prefer descriptive context names over verbose test descriptions
40
+ - Don't open rails console - debug via scripts or runner instead.
data/CHANGELOG.md CHANGED
@@ -1,8 +1,65 @@
1
1
  # Changelog
2
2
 
3
+ ## Unreleased
4
+ * [FEAT] Action class constants are now created eagerly when child classes inherit from parents with mounted actions, allowing direct constant access (e.g., `TeamsharesAPI::Company::Axns::Get.call`)
5
+ * [FEAT] Add ability to determine if currently running in background
6
+ * [FEAT] Handle done! and fail! while executing user blocks
7
+ * [BREAKING] `emit_metrics` hook now receives keyword arguments (`resource:`, `result:`) instead of positional arguments
8
+ * [FEAT] Default `call` method automatically exposes declared exposures by calling methods with matching names - you can now omit `call` entirely when you only need to expose values from private methods
9
+ * [BREAKING] Rename `auto_log` -> `log_calls`
10
+ * [FEAT] Add `log_errors`
11
+ * [FEAT] Add `raise_piping_errors_in_dev` config option to raise framework errors in dev only
12
+ * [BREAKING] Convert profiling from `profile` method to `use :vernier` strategy - profiling now only captures hooks and user code (excludes framework overhead like tracing, logging, timing)
13
+ * [FEAT] Add `set_logging_context` and `additional_logging_context` hook to inject additional context into exception logging
14
+ * [FEAT] Added ActiveSupport::Notification emission for `axn.call_async` (separate from `axn.call`) - emits notification when async jobs are enqueued with payload including resource, action_class, kwargs, and adapter name
15
+ * [INTERNAL] Refactored async adapters to use template method pattern - adapters now implement `_enqueue_async_job` hook instead of overriding `call_async`, eliminating duplication of notification and logging logic
16
+ * [FEAT] Enhanced `error from:` to support arrays of child classes and `from: true` to match any child action - prefix is now optional when using `from:`
17
+ * Improve handling of _async options to call_async (bugfix + serialization improvements)
18
+ * [BREAKING] Replace `enqueue_all_via` block with new `enqueues_each` DSL (now in `Axn::Async`) - declarative batch enqueueing for background job processing
19
+
20
+ ## 0.1.0-alpha.3
21
+ * [FEAT] Added Vernier profiling support with `profile if:` conditional interface and `Axn.config.profiling` configuration
22
+ * [FEAT] Extended model validation to support custom finder methods with `expects :user, model: { klass: User, finder: :find }` syntax
23
+ * [BREAKING] Removed `#try` method
24
+ * [BREAKING] Removed `Axn()` method sugar (use `Axn::Factory.build` directly)
25
+ * [BREAKING] Renamed `Action::Configuration` + `Action.config` -> `Axn::Configuration` + `Axn.config`
26
+ * [BREAKING] Move `Axn::Util` to `Axn::Internal::Logging`
27
+ * [BREAKING] !! Move all `Action` to `Axn` (notably `include Action` is now `include Axn`)
28
+ * [FEAT] Continues to support plain ruby usage, but when used alongside Rails now includes a Rails Engine integrate automatically (e.g. providing generators).
29
+ * Added Rails generator `rails generate axn Some::Action::Name foo bar` to create action classes with expectations
30
+ * Autoload actions from `app/actions` (add config.rails.app_actions_autoload_namespace to allow setting custom namespace)
31
+ * [INTERNAL] Clearer hooks for supporting additional background providers in the future
32
+ * [BREAKING] spec_helpers: removed rarely used `build_axn`; renamed existing `build_action` -> `build_axn`
33
+ * [FEAT] `Axn::Factory.build` can receive a callable OR a block
34
+ * [FEAT] Added `#finalized?` method to `Axn::Result` to check if result has completed execution
35
+ * [FEAT] Added `type: :params` validation option for `expects`/`exposes` that accepts Hash or ActionController::Parameters (Rails-compatible)
36
+ * [FEAT] Allow validations to access instance methods (e.g. `inclusion: { in: :some_method }`)
37
+ * [FEAT] Allow message `prefix` to invoke callables/method name symbols the same way e.g. `if` does
38
+ * [BREAKING] `default`s for `expects` and `exposes` are only applied to explicitly `nil` values (previous applied if given value was blank, which caused bugs for boolean handling)
39
+ * [FEAT] Support `sensitive: true` on subfields (with `on:`)
40
+ * [FEAT] Support `preprocess` on subfields (with `on:`)
41
+ * [FEAT] Support `default` on subfields (with `on:`)
42
+ * [FEAT] Added `#done!` method for early completion with success result
43
+ * [FEAT] Extended `#fail!` and `#done!` methods to accept keyword arguments for exposing data before halting execution
44
+ * [INTERAL] Renamed `Axn::Enqueueable` to `Axn::Async`
45
+ * [BREAKING] Replaced `.enqueue` (only supported sidekiq) with `.call_async` (via a configurable registry of backgrounding libraries)
46
+ * [FEAT] attachable now creates a foo_async to call call_async
47
+ * `type` validator is not still applied to the blank value when allow_blank is true (`type: Hash` will no longer accept `false` or `""`)
48
+ * [FEAT] `expects`/`exposes` now prefers new `optional: true` over allow_blank for simplicity
49
+ * [FEAT] `Axn::Result` now supports Ruby 3's pattern matching feature
50
+ * [FEAT] Extended attachable functionality: added `mount_axn_method` for creating class methods that return values directly instead of wrapped in `Axn::Result`
51
+ * [Internal] Replaced `Axn::Attachable` with `Axn::Mountable` - complete refactor of action mounting system
52
+ * [BREAKING] `#axn` → `#mount_axn` for method mounting
53
+ * [BREAKING] `#axn_method` → `#mount_axn_method` for direct method mounting
54
+ * [NEW] `enqueue_all_via` - Mount batch enqueueing functionality for background job processing
55
+ * [FEAT] Enhanced async execution with job scheduling support
56
+ * [NEW] Support for scheduled async jobs via `_async` parameter with `wait_until:` and `wait:` options
57
+ * [NEW] `enqueue` shortcut methods for all mounted actions
58
+
3
59
  ## 0.1.0-alpha.2.8.1
4
60
  * [BUGFIX] Fixed symbol callback and message handlers not working in inherited classes due to private method visibility issues
5
61
  * [BUGFIX] `default_error` and `default_success` are now properly available for before hooks
62
+ * [FEAT] Support scheduling async jobs (via new `_async` key)
6
63
 
7
64
  ## 0.1.0-alpha.2.8
8
65
  * [FEAT] Custom RuboCop cop `Axn/UncheckedResult` to enforce proper result handling in Actions with configurable nested/non-nested checking
data/Rakefile CHANGED
@@ -6,14 +6,22 @@ require "rspec/core/rake_task"
6
6
  RSpec::Core::RakeTask.new(:spec)
7
7
 
8
8
  # RuboCop specs (separate from main specs to avoid loading RuboCop unnecessarily)
9
- RSpec::Core::RakeTask.new(:spec_rubocop) do |task|
10
- task.pattern = "spec_rubocop/**/*_spec.rb"
9
+ task :spec_rubocop do
10
+ files = Dir.glob("spec_rubocop/**/*_spec.rb")
11
+ sh "bundle exec rspec #{files.join(' ')}"
12
+ end
13
+
14
+ # Rails specs (separate from main specs to avoid loading Rails unnecessarily)
15
+ task :spec_rails do
16
+ Dir.chdir("spec_rails/dummy_app") do
17
+ sh "BUNDLE_GEMFILE=Gemfile bundle exec rspec spec/"
18
+ end
11
19
  end
12
20
 
13
21
  require "rubocop/rake_task"
14
22
 
15
23
  # RuboCop with Axn custom cops (targeting examples/rubocop directory)
16
- task :rubocop_axn do
24
+ task :rubocop_examples do
17
25
  sh "bundle exec rubocop --require axn/rubocop examples/rubocop/ || true"
18
26
  end
19
27
 
@@ -21,4 +29,106 @@ end
21
29
  RuboCop::RakeTask.new
22
30
 
23
31
  task default: %i[spec rubocop]
24
- task all_specs: %i[spec spec_rubocop]
32
+ task rails_specs: %i[spec_rails]
33
+ task rubocop_specs: %i[spec_rubocop]
34
+ task all_specs: %i[spec spec_rubocop spec_rails]
35
+ task specs: %i[all_specs]
36
+
37
+ # Benchmark tasks
38
+ namespace :benchmark do
39
+ desc "Run benchmarks and save results for current gem version (runs automatically after rake release)"
40
+ task :release do
41
+ require_relative "benchmark/support/benchmark_runner"
42
+ require_relative "benchmark/support/storage"
43
+ require_relative "lib/axn/version"
44
+ require_relative "benchmark/support/colors"
45
+
46
+ puts Colors.bold(Colors.info("🔬 Running benchmarks for release..."))
47
+ puts Colors.dim("=" * 80)
48
+ puts ""
49
+
50
+ version = Axn::VERSION
51
+ puts Colors.info("Version: #{version}")
52
+ puts ""
53
+
54
+ # Check if benchmark already exists for this version
55
+ filename = Benchmark::Storage.benchmark_filename(version)
56
+ if File.exist?(filename)
57
+ puts Colors.error("❌ Benchmark file already exists for version #{version}")
58
+ puts Colors.info(" File: #{filename}")
59
+ puts Colors.info(" Delete the file if you want to regenerate benchmarks for this version.")
60
+ abort
61
+ end
62
+
63
+ # Run benchmarks with verbose output
64
+ data = Benchmark::BenchmarkRunner.run_all_scenarios(verbose: true)
65
+
66
+ # Save benchmark data (filename already determined above)
67
+ saved_filename = Benchmark::Storage.save_benchmark(data, version)
68
+ puts ""
69
+ puts Colors.success("✅ Benchmark data saved to: #{saved_filename}")
70
+
71
+ # Update last release version
72
+ Benchmark::Storage.set_last_release_version(version)
73
+ puts Colors.success("✅ Last release version updated to: #{version}")
74
+ puts ""
75
+ puts Colors.dim("=" * 80)
76
+ end
77
+
78
+ desc "Compare current code performance against last release"
79
+ task :compare do
80
+ require_relative "benchmark/support/benchmark_runner"
81
+ require_relative "benchmark/support/storage"
82
+ require_relative "benchmark/support/comparison"
83
+ require_relative "lib/axn/version"
84
+ require_relative "benchmark/support/colors"
85
+
86
+ puts Colors.bold(Colors.info("🔬 Comparing performance against last release..."))
87
+ puts Colors.dim("=" * 80)
88
+ puts ""
89
+
90
+ # Get last release version
91
+ last_release_version = Benchmark::Storage.get_last_release_version
92
+
93
+ if last_release_version.nil?
94
+ puts Colors.error("❌ No last release version found.")
95
+ puts Colors.info(" Run 'rake benchmark:release' after a gem release to create a baseline.")
96
+ exit 1
97
+ end
98
+
99
+ puts Colors.info("Last release version: #{last_release_version}")
100
+ puts ""
101
+
102
+ # Load baseline benchmark
103
+ baseline_data = Benchmark::Storage.load_benchmark(last_release_version)
104
+
105
+ if baseline_data.nil?
106
+ puts Colors.error("❌ Benchmark data not found for version: #{last_release_version}")
107
+ puts Colors.info(" Run 'rake benchmark:release' to create a baseline.")
108
+ exit 1
109
+ end
110
+
111
+ puts Colors.info("Running benchmarks on current code...")
112
+ puts ""
113
+
114
+ # Run current benchmarks (quiet mode for cleaner output)
115
+ current_data = Benchmark::BenchmarkRunner.run_all_scenarios(verbose: false)
116
+
117
+ puts ""
118
+ puts Colors.info("Comparing results...")
119
+ puts ""
120
+
121
+ # Compare and display
122
+ comparison = Benchmark::Comparison.compare(baseline_data, current_data)
123
+ puts Benchmark::Comparison.format_comparison(comparison)
124
+ end
125
+ end
126
+
127
+ # Automatically run benchmark:release after rake release
128
+ Rake::Task["release"].enhance do
129
+ require_relative "benchmark/support/colors"
130
+ puts ""
131
+ puts Colors.bold(Colors.info("🔬 Running benchmarks for released version..."))
132
+ Rake::Task["benchmark:release"].reenable
133
+ Rake::Task["benchmark:release"].invoke
134
+ end
@@ -28,6 +28,7 @@ export default defineConfig({
28
28
  { text: 'Getting Started', link: '/usage/setup' },
29
29
  { text: 'Writing Actions', link: '/usage/writing' },
30
30
  { text: 'Using Actions', link: '/usage/using' },
31
+ { text: 'Steps', link: '/usage/steps' },
31
32
  ]
32
33
  },
33
34
  {
@@ -36,29 +37,37 @@ export default defineConfig({
36
37
  { text: 'Configuration', link: '/reference/configuration' },
37
38
  { text: 'Class Interface', link: '/reference/class' },
38
39
  { text: 'Instance Interface', link: '/reference/instance' },
39
- { text: 'Result Interface', link: '/reference/action-result' },
40
+ { text: 'Result Interface', link: '/reference/axn-result' },
41
+ { text: 'Async', link: '/reference/async' },
42
+ { text: 'FormObject', link: '/reference/form-object' },
40
43
  ]
41
44
  },
42
45
  {
43
- text: 'Recipes',
46
+ text: 'Strategies',
44
47
  items: [
45
- { text: 'Memoization', link: '/recipes/memoization' },
46
- { text: 'Validating User Input', link: '/recipes/validating-user-input' },
47
- { text: 'Testing Actions', link: '/recipes/testing' },
48
+ { text: 'Overview', link: '/strategies/index' },
49
+ { text: 'Transaction', link: '/strategies/transaction' },
50
+ { text: 'Form', link: '/strategies/form' },
51
+ { text: 'Client', link: '/strategies/client' },
48
52
  ]
49
53
  },
50
54
  {
51
- text: 'Strategies',
55
+ text: 'Recipes',
52
56
  items: [
53
- { text: 'Overview', link: '/strategies/index' },
54
- { text: 'Transaction', link: '/strategies/transaction' },
57
+ { text: 'Memoization', link: '/recipes/memoization' },
58
+ { text: 'Validating User Input', link: '/recipes/validating-user-input' },
59
+ { text: 'Testing Actions', link: '/recipes/testing' },
60
+ { text: 'RuboCop Integration', link: '/recipes/rubocop-integration' },
61
+ { text: 'Formatting Context for Error Tracking', link: '/recipes/formatting-context-for-error-tracking' },
55
62
  ]
56
63
  },
57
64
  {
58
- text: 'Additional Notes',
65
+ text: 'Advanced',
59
66
  items: [
60
- { text: 'ROUGH NOTES', link: '/advanced/rough' },
67
+ { text: 'Profiling', link: '/advanced/profiling' },
61
68
  { text: 'Conventions', link: '/advanced/conventions' },
69
+ { text: 'Mountable', link: '/advanced/mountable' },
70
+ { text: 'Internal Notes', link: '/advanced/rough' },
62
71
  ]
63
72
  },
64
73
  ],
@@ -12,7 +12,7 @@ These conventions are still in flux as the library is solidified and we gain mor
12
12
 
13
13
  ## Organizing Actions (Rails)
14
14
 
15
- You _can_ `include Action` into _any_ Ruby class, but to keep track of things we've found it helpful to:
15
+ You _can_ `include Axn` into _any_ Ruby class, but to keep track of things we've found it helpful to:
16
16
 
17
17
  * Create a new `app/actions` folder for our actions
18
18
  * Name them `Actions::[DOMAIN]::[VERB]` where `[DOMAIN]` is a (possibly nested) identifier and `[VERB]` is the action to be taken.
@@ -26,13 +26,13 @@ For us, we've found the maintenance benefits of knowing roughly how the class wi
26
26
  ## Naming conventions
27
27
 
28
28
  ### The responsible user
29
- When tracking _who_ is responsible for the action being taken, one option is to inject it globally via `Current.user` (see: [Current Attributes](https://api.rubyonrails.org/classes/ActiveSupport/CurrentAttributes.html)), but that only works if you're _sure_ you're never going to want to enqueue the job on a background processor.
29
+ When tracking _who_ is responsible for the action being taken, one option is to inject it globally via `Current.user` (see: [Current Attributes](https://api.rubyonrails.org/classes/ActiveSupport/CurrentAttributes.html)), but that only works if you're _sure_ you're never going to want to execute the action asynchronously on a background processor.
30
30
 
31
31
  More generally, we've adopted the convention of passing in the responsible user as `actor`:
32
32
 
33
33
  ```ruby
34
34
  class Foo
35
- include Action
35
+ include Axn
36
36
  expects :actor, type: User # [!code focus]
37
37
  end
38
38
  ```