axn 0.1.0.pre.alpha.3 → 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/CHANGELOG.md +15 -1
- data/Rakefile +102 -2
- data/docs/.vitepress/config.mjs +12 -8
- data/docs/advanced/conventions.md +1 -1
- data/docs/advanced/mountable.md +4 -90
- data/docs/advanced/profiling.md +26 -30
- data/docs/advanced/rough.md +27 -8
- data/docs/intro/overview.md +1 -1
- data/docs/recipes/formatting-context-for-error-tracking.md +186 -0
- data/docs/recipes/memoization.md +102 -17
- data/docs/reference/async.md +269 -0
- data/docs/reference/class.md +113 -50
- data/docs/reference/configuration.md +226 -75
- data/docs/reference/form-object.md +252 -0
- data/docs/strategies/client.md +212 -0
- data/docs/strategies/form.md +235 -0
- data/docs/usage/setup.md +2 -2
- data/docs/usage/writing.md +99 -1
- data/lib/axn/async/adapters/active_job.rb +19 -10
- data/lib/axn/async/adapters/disabled.rb +15 -0
- data/lib/axn/async/adapters/sidekiq.rb +25 -32
- 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 +121 -4
- data/lib/axn/configuration.rb +53 -13
- data/lib/axn/context.rb +1 -0
- data/lib/axn/core/automatic_logging.rb +47 -51
- data/lib/axn/core/context/facade_inspector.rb +1 -1
- data/lib/axn/core/contract.rb +73 -30
- data/lib/axn/core/contract_for_subfields.rb +1 -1
- data/lib/axn/core/contract_validation.rb +14 -9
- data/lib/axn/core/contract_validation_for_subfields.rb +14 -7
- data/lib/axn/core/default_call.rb +63 -0
- data/lib/axn/core/flow/exception_execution.rb +5 -0
- data/lib/axn/core/flow/handlers/descriptors/message_descriptor.rb +19 -7
- data/lib/axn/core/flow/handlers/invoker.rb +4 -30
- data/lib/axn/core/flow/handlers/matcher.rb +4 -14
- data/lib/axn/core/flow/messages.rb +1 -1
- data/lib/axn/core/hooks.rb +1 -0
- data/lib/axn/core/logging.rb +16 -5
- data/lib/axn/core/memoization.rb +53 -0
- data/lib/axn/core/tracing.rb +77 -4
- data/lib/axn/core/validation/validators/type_validator.rb +1 -1
- data/lib/axn/core.rb +31 -46
- 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 +22 -2
- data/lib/axn/form_object.rb +90 -0
- data/lib/axn/internal/logging.rb +5 -1
- data/lib/axn/mountable/helpers/class_builder.rb +41 -10
- data/lib/axn/mountable/helpers/namespace_manager.rb +6 -34
- data/lib/axn/mountable/inherit_profiles.rb +2 -2
- data/lib/axn/mountable/mounting_strategies/_base.rb +10 -6
- data/lib/axn/mountable/mounting_strategies/method.rb +2 -2
- data/lib/axn/mountable.rb +41 -7
- data/lib/axn/rails/generators/axn_generator.rb +19 -1
- data/lib/axn/rails/generators/templates/action.rb.erb +1 -1
- data/lib/axn/result.rb +2 -2
- data/lib/axn/strategies/form.rb +98 -0
- data/lib/axn/strategies/transaction.rb +7 -0
- 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/version.rb +1 -1
- data/lib/axn.rb +9 -0
- metadata +22 -4
- data/lib/axn/core/profiling.rb +0 -124
- data/lib/axn/mountable/mounting_strategies/enqueue_all.rb +0 -55
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
|
data/CHANGELOG.md
CHANGED
|
@@ -1,7 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
|
-
*
|
|
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
|
|
5
19
|
|
|
6
20
|
## 0.1.0-alpha.3
|
|
7
21
|
* [FEAT] Added Vernier profiling support with `profile if:` conditional interface and `Axn.config.profiling` configuration
|
data/Rakefile
CHANGED
|
@@ -6,8 +6,9 @@ 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(' ')}"
|
|
11
12
|
end
|
|
12
13
|
|
|
13
14
|
# Rails specs (separate from main specs to avoid loading Rails unnecessarily)
|
|
@@ -32,3 +33,102 @@ task rails_specs: %i[spec_rails]
|
|
|
32
33
|
task rubocop_specs: %i[spec_rubocop]
|
|
33
34
|
task all_specs: %i[spec spec_rubocop spec_rails]
|
|
34
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
|
@@ -39,6 +39,16 @@ export default defineConfig({
|
|
|
39
39
|
{ text: 'Instance Interface', link: '/reference/instance' },
|
|
40
40
|
{ text: 'Result Interface', link: '/reference/axn-result' },
|
|
41
41
|
{ text: 'Async', link: '/reference/async' },
|
|
42
|
+
{ text: 'FormObject', link: '/reference/form-object' },
|
|
43
|
+
]
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
text: 'Strategies',
|
|
47
|
+
items: [
|
|
48
|
+
{ text: 'Overview', link: '/strategies/index' },
|
|
49
|
+
{ text: 'Transaction', link: '/strategies/transaction' },
|
|
50
|
+
{ text: 'Form', link: '/strategies/form' },
|
|
51
|
+
{ text: 'Client', link: '/strategies/client' },
|
|
42
52
|
]
|
|
43
53
|
},
|
|
44
54
|
{
|
|
@@ -48,13 +58,7 @@ export default defineConfig({
|
|
|
48
58
|
{ text: 'Validating User Input', link: '/recipes/validating-user-input' },
|
|
49
59
|
{ text: 'Testing Actions', link: '/recipes/testing' },
|
|
50
60
|
{ text: 'RuboCop Integration', link: '/recipes/rubocop-integration' },
|
|
51
|
-
|
|
52
|
-
},
|
|
53
|
-
{
|
|
54
|
-
text: 'Strategies',
|
|
55
|
-
items: [
|
|
56
|
-
{ text: 'Overview', link: '/strategies/index' },
|
|
57
|
-
{ text: 'Transaction', link: '/strategies/transaction' },
|
|
61
|
+
{ text: 'Formatting Context for Error Tracking', link: '/recipes/formatting-context-for-error-tracking' },
|
|
58
62
|
]
|
|
59
63
|
},
|
|
60
64
|
{
|
|
@@ -63,7 +67,7 @@ export default defineConfig({
|
|
|
63
67
|
{ text: 'Profiling', link: '/advanced/profiling' },
|
|
64
68
|
{ text: 'Conventions', link: '/advanced/conventions' },
|
|
65
69
|
{ text: 'Mountable', link: '/advanced/mountable' },
|
|
66
|
-
{ text: '
|
|
70
|
+
{ text: 'Internal Notes', link: '/advanced/rough' },
|
|
67
71
|
]
|
|
68
72
|
},
|
|
69
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.
|
data/docs/advanced/mountable.md
CHANGED
|
@@ -116,51 +116,6 @@ end
|
|
|
116
116
|
**Available methods:**
|
|
117
117
|
- `OrderProcessor.call(**kwargs)` - Executes all steps in sequence
|
|
118
118
|
|
|
119
|
-
### `enqueue_all_via`
|
|
120
|
-
|
|
121
|
-
The `enqueue_all_via` method is designed for batch processing scenarios where you need to enqueue multiple instances of an action. It creates methods that can process a collection of items and enqueue each as a separate background job.
|
|
122
|
-
|
|
123
|
-
```ruby
|
|
124
|
-
class SyncForCompany
|
|
125
|
-
include Axn
|
|
126
|
-
|
|
127
|
-
async :sidekiq
|
|
128
|
-
|
|
129
|
-
expects :company_id
|
|
130
|
-
|
|
131
|
-
def call
|
|
132
|
-
company = Company.find(company_id)
|
|
133
|
-
puts "Syncing data for company: #{company.name}"
|
|
134
|
-
# Sync individual company data
|
|
135
|
-
end
|
|
136
|
-
|
|
137
|
-
enqueue_all_via do
|
|
138
|
-
puts "About to enqueue sync jobs for all companies"
|
|
139
|
-
|
|
140
|
-
Company.find_each.map do |company|
|
|
141
|
-
enqueue(company_id: company.id)
|
|
142
|
-
end
|
|
143
|
-
end
|
|
144
|
-
end
|
|
145
|
-
|
|
146
|
-
# Usage
|
|
147
|
-
# Enqueue all companies immediately
|
|
148
|
-
SyncForCompany.enqueue_all
|
|
149
|
-
|
|
150
|
-
# Enqueue the enqueue_all action itself as a background job
|
|
151
|
-
SyncForCompany.enqueue_all_async
|
|
152
|
-
```
|
|
153
|
-
|
|
154
|
-
**Mounted methods:**
|
|
155
|
-
- `SyncForCompany.enqueue_all` - Executes the block immediately and enqueues individual jobs
|
|
156
|
-
- `SyncForCompany.enqueue_all_async` - Enqueues the enqueue_all action itself as a background job
|
|
157
|
-
|
|
158
|
-
#### Key Features
|
|
159
|
-
|
|
160
|
-
- **Inheritance**: Uses `:async_only` mode by default (only inherits async config, nothing else)
|
|
161
|
-
- **enqueue Shortcut**: Use `enqueue` as syntactic sugar for `ClassName.call_async` within the enqueue_all block
|
|
162
|
-
|
|
163
|
-
|
|
164
119
|
## Async Execution
|
|
165
120
|
|
|
166
121
|
Mountable actions automatically support async execution when an async adapter is configured. Each mounted action gets a `_async` method that executes the action in the background.
|
|
@@ -209,7 +164,6 @@ Each mounting strategy has a default inheritance mode that fits its typical use
|
|
|
209
164
|
|
|
210
165
|
- **`mount_axn` and `mount_axn_method`**: Use `:lifecycle` mode (inherits hooks, callbacks, messages, and async config, but not fields)
|
|
211
166
|
- **`step`**: Uses `:none` mode (completely independent to avoid conflicts)
|
|
212
|
-
- **`enqueue_all_via`**: Uses `:async_only` mode (only inherits async configuration for enqueueing)
|
|
213
167
|
|
|
214
168
|
```ruby
|
|
215
169
|
class UserService
|
|
@@ -239,13 +193,6 @@ class UserService
|
|
|
239
193
|
# Will NOT run log_start or track_success
|
|
240
194
|
expose :valid, true
|
|
241
195
|
end
|
|
242
|
-
|
|
243
|
-
# Only inherits async config for enqueueing
|
|
244
|
-
enqueue_all_via do
|
|
245
|
-
# Can call enqueue (uses inherited async config)
|
|
246
|
-
# Does NOT inherit hooks, callbacks, or messages
|
|
247
|
-
User.find_each { |u| enqueue(user_id: u.id) }
|
|
248
|
-
end
|
|
249
196
|
end
|
|
250
197
|
```
|
|
251
198
|
|
|
@@ -276,8 +223,8 @@ end
|
|
|
276
223
|
Only inherits async configuration. Use this for utility methods that need async capability but nothing else:
|
|
277
224
|
|
|
278
225
|
```ruby
|
|
279
|
-
|
|
280
|
-
# Only inherits async config
|
|
226
|
+
mount_axn :background_task, inherit: :async_only do
|
|
227
|
+
# Only inherits async config
|
|
281
228
|
# Completely independent otherwise
|
|
282
229
|
end
|
|
283
230
|
```
|
|
@@ -436,7 +383,7 @@ mount_axn(:user@domain) # Becomes UserDomain constant
|
|
|
436
383
|
- **Use `mount_axn`** when you need full `Axn::Result` objects and error handling
|
|
437
384
|
- **Use `mount_axn_method`** when you want direct return values for simple operations
|
|
438
385
|
- **Use `step`** when composing complex workflows with multiple sequential operations
|
|
439
|
-
- **Use `
|
|
386
|
+
- **Use `enqueues_each`** (from `Axn::Async`) when you need to process multiple items and enqueue each as a separate background job
|
|
440
387
|
|
|
441
388
|
### 2. Keep Actions Focused
|
|
442
389
|
|
|
@@ -526,37 +473,4 @@ end
|
|
|
526
473
|
|
|
527
474
|
### Batch Processing
|
|
528
475
|
|
|
529
|
-
|
|
530
|
-
class EmailProcessor
|
|
531
|
-
include Axn
|
|
532
|
-
|
|
533
|
-
async :sidekiq
|
|
534
|
-
|
|
535
|
-
expects :email_id
|
|
536
|
-
|
|
537
|
-
def call
|
|
538
|
-
email = Email.find(email_id)
|
|
539
|
-
email.deliver!
|
|
540
|
-
end
|
|
541
|
-
|
|
542
|
-
enqueue_all_via do |email_ids:, priority: :normal|
|
|
543
|
-
puts "Processing #{email_ids.count} emails with priority: #{priority}"
|
|
544
|
-
|
|
545
|
-
email_ids.map do |email_id|
|
|
546
|
-
enqueue(email_id: email_id)
|
|
547
|
-
end
|
|
548
|
-
end
|
|
549
|
-
end
|
|
550
|
-
|
|
551
|
-
# Process all pending emails immediately
|
|
552
|
-
EmailProcessor.enqueue_all(
|
|
553
|
-
email_ids: Email.pending.pluck(:id),
|
|
554
|
-
priority: :high
|
|
555
|
-
)
|
|
556
|
-
|
|
557
|
-
# Or enqueue the batch processing as a background job
|
|
558
|
-
EmailProcessor.enqueue_all_async(
|
|
559
|
-
email_ids: Email.pending.pluck(:id),
|
|
560
|
-
priority: :normal
|
|
561
|
-
)
|
|
562
|
-
```
|
|
476
|
+
See `enqueues_each` in the [Async documentation](../reference/async.md) for batch processing with background jobs.
|
data/docs/advanced/profiling.md
CHANGED
|
@@ -32,11 +32,11 @@ bundle install
|
|
|
32
32
|
|
|
33
33
|
### 2. Enable Profiling
|
|
34
34
|
|
|
35
|
-
No global configuration is needed! Simply
|
|
35
|
+
No global configuration is needed! Simply use the `:vernier` strategy on the actions you want to profile.
|
|
36
36
|
|
|
37
37
|
## Basic Usage
|
|
38
38
|
|
|
39
|
-
Profiling is enabled per-action by
|
|
39
|
+
Profiling is enabled per-action by using the `:vernier` strategy. This follows the same pattern as other Axn strategies like `:transaction` and `:form`.
|
|
40
40
|
|
|
41
41
|
### Simple Profiling
|
|
42
42
|
|
|
@@ -47,7 +47,7 @@ class UserCreation
|
|
|
47
47
|
include Axn
|
|
48
48
|
|
|
49
49
|
# Always profile this action
|
|
50
|
-
|
|
50
|
+
use :vernier
|
|
51
51
|
|
|
52
52
|
expects :user_params
|
|
53
53
|
|
|
@@ -72,8 +72,8 @@ Profile only under specific conditions:
|
|
|
72
72
|
class DataProcessing
|
|
73
73
|
include Axn
|
|
74
74
|
|
|
75
|
-
# Profile only when processing large datasets
|
|
76
|
-
|
|
75
|
+
# Profile only when processing large datasets
|
|
76
|
+
use :vernier, if: -> { record_count > 1000 }
|
|
77
77
|
|
|
78
78
|
expects :records, :record_count
|
|
79
79
|
|
|
@@ -89,8 +89,8 @@ end
|
|
|
89
89
|
class DataProcessing
|
|
90
90
|
include Axn
|
|
91
91
|
|
|
92
|
-
# Profile using a method
|
|
93
|
-
|
|
92
|
+
# Profile using a method
|
|
93
|
+
use :vernier, if: :should_profile?
|
|
94
94
|
|
|
95
95
|
expects :records, :record_count, :debug_mode, type: :boolean, default: false
|
|
96
96
|
|
|
@@ -116,7 +116,7 @@ class DevelopmentAction
|
|
|
116
116
|
include Axn
|
|
117
117
|
|
|
118
118
|
# High sampling rate for development (more detailed data)
|
|
119
|
-
|
|
119
|
+
use :vernier, sample_rate: 0.5 if Rails.env.development?
|
|
120
120
|
|
|
121
121
|
def call
|
|
122
122
|
# Action logic
|
|
@@ -127,7 +127,7 @@ class ProductionAction
|
|
|
127
127
|
include Axn
|
|
128
128
|
|
|
129
129
|
# Low sampling rate for production (minimal overhead)
|
|
130
|
-
|
|
130
|
+
use :vernier, sample_rate: 0.01 if Rails.env.production?
|
|
131
131
|
|
|
132
132
|
def call
|
|
133
133
|
# Action logic
|
|
@@ -144,7 +144,7 @@ class MyAction
|
|
|
144
144
|
include Axn
|
|
145
145
|
|
|
146
146
|
# Custom output directory
|
|
147
|
-
|
|
147
|
+
use :vernier, output_dir: Rails.root.join("tmp", "profiles", Rails.env)
|
|
148
148
|
|
|
149
149
|
def call
|
|
150
150
|
# Action logic
|
|
@@ -161,7 +161,7 @@ class ComplexAction
|
|
|
161
161
|
include Axn
|
|
162
162
|
|
|
163
163
|
# Profile when debug mode is enabled OR when processing admin users
|
|
164
|
-
|
|
164
|
+
use :vernier, if: -> { debug_mode || user.admin? }
|
|
165
165
|
|
|
166
166
|
expects :user, :debug_mode, type: :boolean, default: false
|
|
167
167
|
|
|
@@ -219,10 +219,10 @@ Avoid profiling all actions in production:
|
|
|
219
219
|
|
|
220
220
|
```ruby
|
|
221
221
|
# Good: Conditional profiling
|
|
222
|
-
|
|
222
|
+
use :vernier, if: -> { Rails.env.development? || debug_mode }
|
|
223
223
|
|
|
224
224
|
# Avoid: Always profiling in production
|
|
225
|
-
|
|
225
|
+
use :vernier # This can impact performance
|
|
226
226
|
```
|
|
227
227
|
|
|
228
228
|
### 2. Appropriate Sampling Rates
|
|
@@ -234,13 +234,13 @@ class MyAction
|
|
|
234
234
|
include Axn
|
|
235
235
|
|
|
236
236
|
# High detail for debugging
|
|
237
|
-
|
|
237
|
+
use :vernier, sample_rate: 0.5 if Rails.env.development?
|
|
238
238
|
|
|
239
239
|
# Moderate sampling for staging
|
|
240
|
-
|
|
240
|
+
use :vernier, sample_rate: 0.1 if Rails.env.staging?
|
|
241
241
|
|
|
242
242
|
# Minimal overhead for production
|
|
243
|
-
|
|
243
|
+
use :vernier, sample_rate: 0.01 if Rails.env.production?
|
|
244
244
|
|
|
245
245
|
def call
|
|
246
246
|
# Action logic
|
|
@@ -257,7 +257,7 @@ class OrderProcessing
|
|
|
257
257
|
include Axn
|
|
258
258
|
|
|
259
259
|
# Profile only expensive operations
|
|
260
|
-
|
|
260
|
+
use :vernier, if: -> { order.total > 1000 }
|
|
261
261
|
|
|
262
262
|
expects :order
|
|
263
263
|
|
|
@@ -305,7 +305,7 @@ Make sure to:
|
|
|
305
305
|
|
|
306
306
|
If profile files aren't being generated:
|
|
307
307
|
|
|
308
|
-
1. Verify your action has `
|
|
308
|
+
1. Verify your action has `use :vernier` enabled
|
|
309
309
|
2. Ensure profiling conditions are met
|
|
310
310
|
3. Check the output directory exists and is writable
|
|
311
311
|
|
|
@@ -321,32 +321,28 @@ Use appropriate sampling rates and conditional profiling to minimize impact.
|
|
|
321
321
|
|
|
322
322
|
## Integration with Other Tools
|
|
323
323
|
|
|
324
|
-
### Datadog Integration
|
|
324
|
+
### OpenTelemetry and Datadog Integration
|
|
325
325
|
|
|
326
|
-
|
|
326
|
+
Axn automatically creates OpenTelemetry spans for all actions when OpenTelemetry is available. These spans appear as children of your Rails request traces in APM tools.
|
|
327
327
|
|
|
328
|
-
|
|
329
|
-
Axn.configure do |c|
|
|
330
|
-
# Datadog tracing
|
|
331
|
-
c.wrap_with_trace = proc do |resource, &action|
|
|
332
|
-
Datadog::Tracing.trace("Action", resource:) do
|
|
333
|
-
action.call
|
|
334
|
-
end
|
|
335
|
-
end
|
|
336
|
-
end
|
|
328
|
+
You can combine profiling with OpenTelemetry tracing:
|
|
337
329
|
|
|
330
|
+
```ruby
|
|
338
331
|
class MyAction
|
|
339
332
|
include Axn
|
|
340
333
|
|
|
341
334
|
# Profiling with custom options
|
|
342
|
-
|
|
335
|
+
use :vernier, sample_rate: 0.1
|
|
343
336
|
|
|
344
337
|
def call
|
|
345
338
|
# Action logic
|
|
339
|
+
# OpenTelemetry spans are automatically created
|
|
346
340
|
end
|
|
347
341
|
end
|
|
348
342
|
```
|
|
349
343
|
|
|
344
|
+
For detailed setup instructions on sending traces to Datadog (including the required gems and initialization order), see the [OpenTelemetry Tracing section](/reference/configuration#opentelemetry-tracing) in the Configuration reference.
|
|
345
|
+
|
|
350
346
|
## Resources
|
|
351
347
|
|
|
352
348
|
- [Vernier GitHub Repository](https://github.com/Shopify/vernier)
|
data/docs/advanced/rough.md
CHANGED
|
@@ -1,14 +1,33 @@
|
|
|
1
|
-
|
|
2
|
-
* TODO: convert rough notes into actual documentation
|
|
3
|
-
:::
|
|
1
|
+
# Internal Notes
|
|
4
2
|
|
|
5
|
-
|
|
3
|
+
This page contains internal implementation notes for contributors and advanced users.
|
|
6
4
|
|
|
7
|
-
|
|
5
|
+
## Context Sharing
|
|
8
6
|
|
|
9
|
-
|
|
7
|
+
The inbound/outbound contexts are views into an underlying shared object. Modifications to one affect the other:
|
|
10
8
|
|
|
11
|
-
|
|
9
|
+
- Preprocessing inbound args implicitly transforms them on the underlying context
|
|
10
|
+
- If you also expose a preprocessed field on outbound, it will reflect the transformed value
|
|
12
11
|
|
|
13
|
-
|
|
12
|
+
## Logging and Debugging
|
|
14
13
|
|
|
14
|
+
For information about logging configuration, see the [Configuration reference](/reference/configuration):
|
|
15
|
+
|
|
16
|
+
- **Logger configuration**: [logger](/reference/configuration#logger)
|
|
17
|
+
- **Log levels**: [log_level](/reference/configuration#log-level)
|
|
18
|
+
- **Automatic logging**: [Automatic Logging](/reference/configuration#automatic-logging)
|
|
19
|
+
|
|
20
|
+
### `context_for_logging`
|
|
21
|
+
|
|
22
|
+
The `context_for_logging` method returns a hash of the action's context, with:
|
|
23
|
+
- Filtering to accessible attributes
|
|
24
|
+
- Sensitive values removed (fields marked with `sensitive: true`)
|
|
25
|
+
|
|
26
|
+
This is automatically passed to the `on_exception` hook. See [Adding Additional Context to Exception Logging](/reference/configuration#adding-additional-context-to-exception-logging) for customizing the context.
|
|
27
|
+
|
|
28
|
+
### `#inspect` Support
|
|
29
|
+
|
|
30
|
+
Action instances provide a readable `#inspect` output that shows:
|
|
31
|
+
- The action class name
|
|
32
|
+
- Field values (with sensitive values filtered)
|
|
33
|
+
- Current execution state
|
data/docs/intro/overview.md
CHANGED
|
@@ -13,7 +13,7 @@ This library provides a set of conventions for writing business logic in Rails (
|
|
|
13
13
|
|
|
14
14
|
### Minimal example
|
|
15
15
|
|
|
16
|
-
Your logic goes in a <abbr title="Plain Old Ruby Object">PORO</abbr>. The only requirements are to `include
|
|
16
|
+
Your logic goes in a <abbr title="Plain Old Ruby Object">PORO</abbr>. The only requirements are to `include Axn` and define a `call` method, meaning the basic skeleton looks something like this:
|
|
17
17
|
|
|
18
18
|
```ruby
|
|
19
19
|
class Foo
|