axn 0.1.0.pre.alpha.2.8.1 → 0.1.0.pre.alpha.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.cursor/commands/pr.md +36 -0
- data/.cursor/rules/axn-framework-patterns.mdc +43 -0
- data/.cursor/rules/general-coding-standards.mdc +27 -0
- data/.cursor/rules/spec/testing-patterns.mdc +40 -0
- data/CHANGELOG.md +57 -0
- data/Rakefile +114 -4
- data/docs/.vitepress/config.mjs +19 -10
- data/docs/advanced/conventions.md +3 -3
- data/docs/advanced/mountable.md +476 -0
- data/docs/advanced/profiling.md +351 -0
- data/docs/advanced/rough.md +27 -8
- data/docs/index.md +5 -3
- data/docs/intro/about.md +1 -1
- data/docs/intro/overview.md +6 -6
- data/docs/recipes/formatting-context-for-error-tracking.md +186 -0
- data/docs/recipes/memoization.md +103 -18
- data/docs/recipes/rubocop-integration.md +38 -284
- data/docs/recipes/testing.md +14 -14
- data/docs/recipes/validating-user-input.md +1 -1
- data/docs/reference/async.md +429 -0
- data/docs/reference/axn-result.md +107 -0
- data/docs/reference/class.md +225 -64
- data/docs/reference/configuration.md +366 -34
- data/docs/reference/form-object.md +252 -0
- data/docs/reference/instance.md +14 -29
- data/docs/strategies/client.md +212 -0
- data/docs/strategies/form.md +235 -0
- data/docs/strategies/index.md +21 -21
- data/docs/strategies/transaction.md +1 -1
- data/docs/usage/setup.md +16 -2
- data/docs/usage/steps.md +7 -7
- data/docs/usage/using.md +23 -12
- data/docs/usage/writing.md +191 -12
- data/lib/axn/async/adapters/active_job.rb +74 -0
- data/lib/axn/async/adapters/disabled.rb +41 -0
- data/lib/axn/async/adapters/sidekiq.rb +67 -0
- data/lib/axn/async/adapters.rb +26 -0
- data/lib/axn/async/batch_enqueue/config.rb +38 -0
- data/lib/axn/async/batch_enqueue.rb +99 -0
- data/lib/axn/async/enqueue_all_orchestrator.rb +363 -0
- data/lib/axn/async.rb +178 -0
- data/lib/axn/configuration.rb +113 -0
- data/lib/{action → axn}/context.rb +22 -4
- data/lib/axn/core/automatic_logging.rb +89 -0
- data/lib/axn/core/context/facade.rb +69 -0
- data/lib/{action → axn}/core/context/facade_inspector.rb +32 -5
- data/lib/{action → axn}/core/context/internal.rb +5 -5
- data/lib/{action → axn}/core/contract.rb +111 -73
- data/lib/{action → axn}/core/contract_for_subfields.rb +30 -35
- data/lib/{action → axn}/core/contract_validation.rb +27 -12
- data/lib/axn/core/contract_validation_for_subfields.rb +165 -0
- data/lib/axn/core/default_call.rb +63 -0
- data/lib/axn/core/field_resolvers/extract.rb +32 -0
- data/lib/axn/core/field_resolvers/model.rb +63 -0
- data/lib/axn/core/field_resolvers.rb +24 -0
- data/lib/{action → axn}/core/flow/callbacks.rb +7 -7
- data/lib/{action → axn}/core/flow/exception_execution.rb +9 -13
- data/lib/{action → axn}/core/flow/handlers/base_descriptor.rb +3 -2
- data/lib/{action → axn}/core/flow/handlers/descriptors/callback_descriptor.rb +2 -2
- data/lib/{action → axn}/core/flow/handlers/descriptors/message_descriptor.rb +23 -11
- data/lib/axn/core/flow/handlers/invoker.rb +47 -0
- data/lib/{action → axn}/core/flow/handlers/matcher.rb +9 -19
- data/lib/{action → axn}/core/flow/handlers/registry.rb +3 -1
- data/lib/{action → axn}/core/flow/handlers/resolvers/base_resolver.rb +1 -1
- data/lib/{action → axn}/core/flow/handlers/resolvers/callback_resolver.rb +2 -2
- data/lib/{action → axn}/core/flow/handlers/resolvers/message_resolver.rb +12 -3
- data/lib/axn/core/flow/handlers.rb +20 -0
- data/lib/{action → axn}/core/flow/messages.rb +8 -8
- data/lib/{action → axn}/core/flow.rb +4 -4
- data/lib/{action → axn}/core/hooks.rb +17 -5
- data/lib/axn/core/logging.rb +48 -0
- data/lib/axn/core/memoization.rb +53 -0
- data/lib/{action → axn}/core/nesting_tracking.rb +1 -1
- data/lib/{action → axn}/core/timing.rb +1 -1
- data/lib/axn/core/tracing.rb +90 -0
- data/lib/axn/core/use_strategy.rb +29 -0
- data/lib/{action → axn}/core/validation/fields.rb +26 -2
- data/lib/{action → axn}/core/validation/subfields.rb +14 -12
- data/lib/axn/core/validation/validators/model_validator.rb +36 -0
- data/lib/axn/core/validation/validators/type_validator.rb +80 -0
- data/lib/{action → axn}/core/validation/validators/validate_validator.rb +12 -2
- data/lib/{action → axn}/core.rb +55 -55
- data/lib/{action → axn}/exceptions.rb +12 -2
- data/lib/axn/extras/strategies/client.rb +150 -0
- data/lib/axn/extras/strategies/vernier.rb +121 -0
- data/lib/axn/extras.rb +4 -0
- data/lib/axn/factory.rb +122 -34
- data/lib/axn/form_object.rb +90 -0
- data/lib/axn/internal/logging.rb +30 -0
- data/lib/axn/internal/registry.rb +87 -0
- data/lib/axn/mountable/descriptor.rb +76 -0
- data/lib/axn/mountable/helpers/class_builder.rb +193 -0
- data/lib/axn/mountable/helpers/mounter.rb +33 -0
- data/lib/axn/mountable/helpers/namespace_manager.rb +38 -0
- data/lib/axn/mountable/helpers/validator.rb +112 -0
- data/lib/axn/mountable/inherit_profiles.rb +72 -0
- data/lib/axn/mountable/mounting_strategies/_base.rb +87 -0
- data/lib/axn/mountable/mounting_strategies/axn.rb +48 -0
- data/lib/axn/mountable/mounting_strategies/method.rb +95 -0
- data/lib/axn/mountable/mounting_strategies/step.rb +69 -0
- data/lib/axn/mountable/mounting_strategies.rb +32 -0
- data/lib/axn/mountable.rb +119 -0
- data/lib/axn/rails/engine.rb +51 -0
- data/lib/axn/rails/generators/axn_generator.rb +86 -0
- data/lib/axn/rails/generators/templates/action.rb.erb +17 -0
- data/lib/axn/rails/generators/templates/action_spec.rb.erb +25 -0
- data/lib/{action → axn}/result.rb +32 -13
- data/lib/axn/strategies/form.rb +98 -0
- data/lib/axn/strategies/transaction.rb +26 -0
- data/lib/axn/strategies.rb +20 -0
- data/lib/axn/testing/spec_helpers.rb +6 -8
- data/lib/axn/util/callable.rb +120 -0
- data/lib/axn/util/contract_error_handling.rb +32 -0
- data/lib/axn/util/execution_context.rb +34 -0
- data/lib/axn/util/global_id_serialization.rb +52 -0
- data/lib/axn/util/logging.rb +87 -0
- data/lib/axn/util/memoization.rb +20 -0
- data/lib/axn/version.rb +1 -1
- data/lib/axn.rb +26 -16
- data/lib/rubocop/cop/axn/README.md +23 -23
- data/lib/rubocop/cop/axn/unchecked_result.rb +138 -17
- metadata +106 -64
- data/.rspec +0 -3
- data/.rubocop.yml +0 -76
- data/.tool-versions +0 -1
- data/docs/reference/action-result.md +0 -37
- data/lib/action/attachable/base.rb +0 -43
- data/lib/action/attachable/steps.rb +0 -63
- data/lib/action/attachable/subactions.rb +0 -70
- data/lib/action/attachable.rb +0 -17
- data/lib/action/configuration.rb +0 -55
- data/lib/action/core/automatic_logging.rb +0 -93
- data/lib/action/core/context/facade.rb +0 -48
- data/lib/action/core/flow/handlers/invoker.rb +0 -73
- data/lib/action/core/flow/handlers.rb +0 -20
- data/lib/action/core/logging.rb +0 -37
- data/lib/action/core/tracing.rb +0 -17
- data/lib/action/core/use_strategy.rb +0 -30
- data/lib/action/core/validation/validators/model_validator.rb +0 -34
- data/lib/action/core/validation/validators/type_validator.rb +0 -30
- data/lib/action/enqueueable/via_sidekiq.rb +0 -76
- data/lib/action/enqueueable.rb +0 -13
- data/lib/action/strategies/transaction.rb +0 -19
- data/lib/action/strategies.rb +0 -48
- data/lib/axn/util.rb +0 -24
- data/package.json +0 -10
- data/yarn.lock +0 -1166
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8930af546a87917b02eb7c4af3fdc88670f6b323cbf9e7223553eca07ac6bf8c
|
|
4
|
+
data.tar.gz: 47a70ea981ae8c9a7405e02c844174996c0d1a3b9788cbf5c37d725d77f00d5c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
-
|
|
10
|
-
|
|
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 :
|
|
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
|
|
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
|
data/docs/.vitepress/config.mjs
CHANGED
|
@@ -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/
|
|
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: '
|
|
46
|
+
text: 'Strategies',
|
|
44
47
|
items: [
|
|
45
|
-
{ text: '
|
|
46
|
-
{ text: '
|
|
47
|
-
{ text: '
|
|
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: '
|
|
55
|
+
text: 'Recipes',
|
|
52
56
|
items: [
|
|
53
|
-
{ text: '
|
|
54
|
-
{ text: '
|
|
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: '
|
|
65
|
+
text: 'Advanced',
|
|
59
66
|
items: [
|
|
60
|
-
{ text: '
|
|
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
|
|
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
|
|
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
|
|
35
|
+
include Axn
|
|
36
36
|
expects :actor, type: User # [!code focus]
|
|
37
37
|
end
|
|
38
38
|
```
|