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.
Files changed (74) hide show
  1. checksums.yaml +4 -4
  2. data/.cursor/commands/pr.md +36 -0
  3. data/CHANGELOG.md +15 -1
  4. data/Rakefile +102 -2
  5. data/docs/.vitepress/config.mjs +12 -8
  6. data/docs/advanced/conventions.md +1 -1
  7. data/docs/advanced/mountable.md +4 -90
  8. data/docs/advanced/profiling.md +26 -30
  9. data/docs/advanced/rough.md +27 -8
  10. data/docs/intro/overview.md +1 -1
  11. data/docs/recipes/formatting-context-for-error-tracking.md +186 -0
  12. data/docs/recipes/memoization.md +102 -17
  13. data/docs/reference/async.md +269 -0
  14. data/docs/reference/class.md +113 -50
  15. data/docs/reference/configuration.md +226 -75
  16. data/docs/reference/form-object.md +252 -0
  17. data/docs/strategies/client.md +212 -0
  18. data/docs/strategies/form.md +235 -0
  19. data/docs/usage/setup.md +2 -2
  20. data/docs/usage/writing.md +99 -1
  21. data/lib/axn/async/adapters/active_job.rb +19 -10
  22. data/lib/axn/async/adapters/disabled.rb +15 -0
  23. data/lib/axn/async/adapters/sidekiq.rb +25 -32
  24. data/lib/axn/async/batch_enqueue/config.rb +38 -0
  25. data/lib/axn/async/batch_enqueue.rb +99 -0
  26. data/lib/axn/async/enqueue_all_orchestrator.rb +363 -0
  27. data/lib/axn/async.rb +121 -4
  28. data/lib/axn/configuration.rb +53 -13
  29. data/lib/axn/context.rb +1 -0
  30. data/lib/axn/core/automatic_logging.rb +47 -51
  31. data/lib/axn/core/context/facade_inspector.rb +1 -1
  32. data/lib/axn/core/contract.rb +73 -30
  33. data/lib/axn/core/contract_for_subfields.rb +1 -1
  34. data/lib/axn/core/contract_validation.rb +14 -9
  35. data/lib/axn/core/contract_validation_for_subfields.rb +14 -7
  36. data/lib/axn/core/default_call.rb +63 -0
  37. data/lib/axn/core/flow/exception_execution.rb +5 -0
  38. data/lib/axn/core/flow/handlers/descriptors/message_descriptor.rb +19 -7
  39. data/lib/axn/core/flow/handlers/invoker.rb +4 -30
  40. data/lib/axn/core/flow/handlers/matcher.rb +4 -14
  41. data/lib/axn/core/flow/messages.rb +1 -1
  42. data/lib/axn/core/hooks.rb +1 -0
  43. data/lib/axn/core/logging.rb +16 -5
  44. data/lib/axn/core/memoization.rb +53 -0
  45. data/lib/axn/core/tracing.rb +77 -4
  46. data/lib/axn/core/validation/validators/type_validator.rb +1 -1
  47. data/lib/axn/core.rb +31 -46
  48. data/lib/axn/extras/strategies/client.rb +150 -0
  49. data/lib/axn/extras/strategies/vernier.rb +121 -0
  50. data/lib/axn/extras.rb +4 -0
  51. data/lib/axn/factory.rb +22 -2
  52. data/lib/axn/form_object.rb +90 -0
  53. data/lib/axn/internal/logging.rb +5 -1
  54. data/lib/axn/mountable/helpers/class_builder.rb +41 -10
  55. data/lib/axn/mountable/helpers/namespace_manager.rb +6 -34
  56. data/lib/axn/mountable/inherit_profiles.rb +2 -2
  57. data/lib/axn/mountable/mounting_strategies/_base.rb +10 -6
  58. data/lib/axn/mountable/mounting_strategies/method.rb +2 -2
  59. data/lib/axn/mountable.rb +41 -7
  60. data/lib/axn/rails/generators/axn_generator.rb +19 -1
  61. data/lib/axn/rails/generators/templates/action.rb.erb +1 -1
  62. data/lib/axn/result.rb +2 -2
  63. data/lib/axn/strategies/form.rb +98 -0
  64. data/lib/axn/strategies/transaction.rb +7 -0
  65. data/lib/axn/util/callable.rb +120 -0
  66. data/lib/axn/util/contract_error_handling.rb +32 -0
  67. data/lib/axn/util/execution_context.rb +34 -0
  68. data/lib/axn/util/global_id_serialization.rb +52 -0
  69. data/lib/axn/util/logging.rb +87 -0
  70. data/lib/axn/version.rb +1 -1
  71. data/lib/axn.rb +9 -0
  72. metadata +22 -4
  73. data/lib/axn/core/profiling.rb +0 -124
  74. 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: 62c8aec52fd200af8f2c3a7cd19c9a589c5353bca7bd6ed6ca08dbc990b381af
4
- data.tar.gz: 0bae864b6bf77b4f5200f2671bbed65ff8fea1f22fbbaa9985e088d4be82d767
3
+ metadata.gz: 8930af546a87917b02eb7c4af3fdc88670f6b323cbf9e7223553eca07ac6bf8c
4
+ data.tar.gz: 47a70ea981ae8c9a7405e02c844174996c0d1a3b9788cbf5c37d725d77f00d5c
5
5
  SHA512:
6
- metadata.gz: 6fa6cabde451cec1eda5600f29131cbe78b7f0e66742bed84257e83d0b45418fc300452b24147a9bb4f319710c87da02ac541c17312e17993e1ea636c09b52c6
7
- data.tar.gz: 4ee9dd287f81d027e0538fa94c959ffca9e560274a7d558196f163e7569f9ea7eae996857c55f5b0453a3a876863fa5b384171d368907c1c1c53ec30f72366ea
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
- * N/A
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
- 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(' ')}"
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
@@ -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: 'ROUGH NOTES', link: '/advanced/rough' },
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 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.
@@ -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
- enqueue_all_via inherit: :async_only do
280
- # Only inherits async config for enqueueing
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 `enqueue_all_via`** when you need to process multiple items and enqueue each as a separate background job
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
- ```ruby
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.
@@ -32,11 +32,11 @@ bundle install
32
32
 
33
33
  ### 2. Enable Profiling
34
34
 
35
- No global configuration is needed! Simply call `profile` on the actions you want to profile.
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 calling the `profile` method. You can only call `profile` **once per action** - subsequent calls will override the previous one. This prevents accidental profiling of all actions and ensures you only profile what you intend to analyze.
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
- profile
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 (only one profile call per action)
76
- profile if: -> { record_count > 1000 }
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 (only one profile call per action)
93
- profile if: :should_profile?
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
- profile(sample_rate: 0.5) if Rails.env.development?
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
- profile(sample_rate: 0.01) if Rails.env.production?
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
- profile(output_dir: Rails.root.join("tmp", "profiles", Rails.env))
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
- profile if: -> { debug_mode || user.admin? }
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
- profile if: -> { Rails.env.development? || debug_mode }
222
+ use :vernier, if: -> { Rails.env.development? || debug_mode }
223
223
 
224
224
  # Avoid: Always profiling in production
225
- profile # This can impact performance
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
- profile(sample_rate: 0.5) if Rails.env.development?
237
+ use :vernier, sample_rate: 0.5 if Rails.env.development?
238
238
 
239
239
  # Moderate sampling for staging
240
- profile(sample_rate: 0.1) if Rails.env.staging?
240
+ use :vernier, sample_rate: 0.1 if Rails.env.staging?
241
241
 
242
242
  # Minimal overhead for production
243
- profile(sample_rate: 0.01) if Rails.env.production?
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
- profile if: -> { order.total > 1000 }
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 `profile` enabled
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
- Combine profiling with Datadog tracing:
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
- ```ruby
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
- profile(sample_rate: 0.1)
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)
@@ -1,14 +1,33 @@
1
- ::: danger ALPHA
2
- * TODO: convert rough notes into actual documentation
3
- :::
1
+ # Internal Notes
4
2
 
5
- ## Rough Notes
3
+ This page contains internal implementation notes for contributors and advanced users.
6
4
 
7
- * General note: the inbound/outbound contexts are views into an underlying shared object (passed down through organize calls) -- modifications of one will affect the other (e.g. preprocessing inbound args implicitly transforms them on the underlying context, which is echoed if you also expose it on outbound).
5
+ ## Context Sharing
8
6
 
9
- * `context_for_logging` (and decent #inspect support)
7
+ The inbound/outbound contexts are views into an underlying shared object. Modifications to one affect the other:
10
8
 
11
- * Configuring logging (will default to Rails.logger if available, else fall back to basic Logger (but can explicitly set via e.g. `Axn.config.logger = Logger.new($stdout`))
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
- * Note `context_for_logging` is available (filtered to accessible attrs, filtering out sensitive values). Automatically passed into `on_exception` hook.
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
@@ -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 Action` and a `call` method, meaning the basic skeleton looks something like this:
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