axn 0.1.0.pre.alpha.2.8.1 → 0.1.0.pre.alpha.3

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 (126) hide show
  1. checksums.yaml +4 -4
  2. data/.cursor/rules/axn-framework-patterns.mdc +43 -0
  3. data/.cursor/rules/general-coding-standards.mdc +27 -0
  4. data/.cursor/rules/spec/testing-patterns.mdc +40 -0
  5. data/CHANGELOG.md +43 -0
  6. data/Rakefile +12 -2
  7. data/docs/.vitepress/config.mjs +8 -3
  8. data/docs/advanced/conventions.md +2 -2
  9. data/docs/advanced/mountable.md +562 -0
  10. data/docs/advanced/profiling.md +355 -0
  11. data/docs/advanced/rough.md +1 -1
  12. data/docs/index.md +5 -3
  13. data/docs/intro/about.md +1 -1
  14. data/docs/intro/overview.md +5 -5
  15. data/docs/recipes/memoization.md +2 -2
  16. data/docs/recipes/rubocop-integration.md +38 -284
  17. data/docs/recipes/testing.md +14 -14
  18. data/docs/recipes/validating-user-input.md +1 -1
  19. data/docs/reference/async.md +160 -0
  20. data/docs/reference/axn-result.md +107 -0
  21. data/docs/reference/class.md +123 -25
  22. data/docs/reference/configuration.md +191 -10
  23. data/docs/reference/instance.md +14 -29
  24. data/docs/strategies/index.md +21 -21
  25. data/docs/strategies/transaction.md +1 -1
  26. data/docs/usage/setup.md +14 -0
  27. data/docs/usage/steps.md +7 -7
  28. data/docs/usage/using.md +23 -12
  29. data/docs/usage/writing.md +92 -11
  30. data/lib/axn/async/adapters/active_job.rb +65 -0
  31. data/lib/axn/async/adapters/disabled.rb +26 -0
  32. data/lib/axn/async/adapters/sidekiq.rb +74 -0
  33. data/lib/axn/async/adapters.rb +26 -0
  34. data/lib/axn/async.rb +61 -0
  35. data/lib/{action → axn}/configuration.rb +21 -3
  36. data/lib/{action → axn}/context.rb +21 -4
  37. data/lib/{action → axn}/core/automatic_logging.rb +6 -6
  38. data/lib/axn/core/context/facade.rb +69 -0
  39. data/lib/{action → axn}/core/context/facade_inspector.rb +31 -4
  40. data/lib/{action → axn}/core/context/internal.rb +5 -5
  41. data/lib/{action → axn}/core/contract.rb +41 -46
  42. data/lib/{action → axn}/core/contract_for_subfields.rb +30 -35
  43. data/lib/{action → axn}/core/contract_validation.rb +16 -6
  44. data/lib/axn/core/contract_validation_for_subfields.rb +158 -0
  45. data/lib/axn/core/field_resolvers/extract.rb +32 -0
  46. data/lib/axn/core/field_resolvers/model.rb +63 -0
  47. data/lib/axn/core/field_resolvers.rb +24 -0
  48. data/lib/{action → axn}/core/flow/callbacks.rb +7 -7
  49. data/lib/{action → axn}/core/flow/exception_execution.rb +4 -13
  50. data/lib/{action → axn}/core/flow/handlers/base_descriptor.rb +3 -2
  51. data/lib/{action → axn}/core/flow/handlers/descriptors/callback_descriptor.rb +2 -2
  52. data/lib/{action → axn}/core/flow/handlers/descriptors/message_descriptor.rb +6 -6
  53. data/lib/{action → axn}/core/flow/handlers/invoker.rb +2 -2
  54. data/lib/{action → axn}/core/flow/handlers/matcher.rb +5 -5
  55. data/lib/{action → axn}/core/flow/handlers/registry.rb +3 -1
  56. data/lib/{action → axn}/core/flow/handlers/resolvers/base_resolver.rb +1 -1
  57. data/lib/{action → axn}/core/flow/handlers/resolvers/callback_resolver.rb +2 -2
  58. data/lib/{action → axn}/core/flow/handlers/resolvers/message_resolver.rb +12 -3
  59. data/lib/axn/core/flow/handlers.rb +20 -0
  60. data/lib/{action → axn}/core/flow/messages.rb +7 -7
  61. data/lib/{action → axn}/core/flow.rb +4 -4
  62. data/lib/{action → axn}/core/hooks.rb +16 -5
  63. data/lib/{action → axn}/core/logging.rb +3 -3
  64. data/lib/{action → axn}/core/nesting_tracking.rb +1 -1
  65. data/lib/axn/core/profiling.rb +124 -0
  66. data/lib/{action → axn}/core/timing.rb +1 -1
  67. data/lib/axn/core/tracing.rb +17 -0
  68. data/lib/axn/core/use_strategy.rb +29 -0
  69. data/lib/{action → axn}/core/validation/fields.rb +26 -2
  70. data/lib/{action → axn}/core/validation/subfields.rb +14 -12
  71. data/lib/axn/core/validation/validators/model_validator.rb +36 -0
  72. data/lib/axn/core/validation/validators/type_validator.rb +80 -0
  73. data/lib/{action → axn}/core/validation/validators/validate_validator.rb +12 -2
  74. data/lib/axn/core.rb +123 -0
  75. data/lib/{action → axn}/exceptions.rb +12 -2
  76. data/lib/axn/factory.rb +102 -34
  77. data/lib/axn/internal/logging.rb +26 -0
  78. data/lib/axn/internal/registry.rb +87 -0
  79. data/lib/axn/mountable/descriptor.rb +76 -0
  80. data/lib/axn/mountable/helpers/class_builder.rb +162 -0
  81. data/lib/axn/mountable/helpers/mounter.rb +33 -0
  82. data/lib/axn/mountable/helpers/namespace_manager.rb +66 -0
  83. data/lib/axn/mountable/helpers/validator.rb +112 -0
  84. data/lib/axn/mountable/inherit_profiles.rb +72 -0
  85. data/lib/axn/mountable/mounting_strategies/_base.rb +83 -0
  86. data/lib/axn/mountable/mounting_strategies/axn.rb +48 -0
  87. data/lib/axn/mountable/mounting_strategies/enqueue_all.rb +55 -0
  88. data/lib/axn/mountable/mounting_strategies/method.rb +95 -0
  89. data/lib/axn/mountable/mounting_strategies/step.rb +69 -0
  90. data/lib/axn/mountable/mounting_strategies.rb +32 -0
  91. data/lib/axn/mountable.rb +85 -0
  92. data/lib/axn/rails/engine.rb +51 -0
  93. data/lib/axn/rails/generators/axn_generator.rb +68 -0
  94. data/lib/axn/rails/generators/templates/action.rb.erb +17 -0
  95. data/lib/axn/rails/generators/templates/action_spec.rb.erb +25 -0
  96. data/lib/{action → axn}/result.rb +30 -11
  97. data/lib/{action → axn}/strategies/transaction.rb +1 -1
  98. data/lib/axn/strategies.rb +20 -0
  99. data/lib/axn/testing/spec_helpers.rb +6 -8
  100. data/lib/axn/util/memoization.rb +20 -0
  101. data/lib/axn/version.rb +1 -1
  102. data/lib/axn.rb +17 -16
  103. data/lib/rubocop/cop/axn/README.md +23 -23
  104. data/lib/rubocop/cop/axn/unchecked_result.rb +138 -17
  105. metadata +88 -64
  106. data/.rspec +0 -3
  107. data/.rubocop.yml +0 -76
  108. data/.tool-versions +0 -1
  109. data/docs/reference/action-result.md +0 -37
  110. data/lib/action/attachable/base.rb +0 -43
  111. data/lib/action/attachable/steps.rb +0 -63
  112. data/lib/action/attachable/subactions.rb +0 -70
  113. data/lib/action/attachable.rb +0 -17
  114. data/lib/action/core/context/facade.rb +0 -48
  115. data/lib/action/core/flow/handlers.rb +0 -20
  116. data/lib/action/core/tracing.rb +0 -17
  117. data/lib/action/core/use_strategy.rb +0 -30
  118. data/lib/action/core/validation/validators/model_validator.rb +0 -34
  119. data/lib/action/core/validation/validators/type_validator.rb +0 -30
  120. data/lib/action/core.rb +0 -108
  121. data/lib/action/enqueueable/via_sidekiq.rb +0 -76
  122. data/lib/action/enqueueable.rb +0 -13
  123. data/lib/action/strategies.rb +0 -48
  124. data/lib/axn/util.rb +0 -24
  125. data/package.json +0 -10
  126. 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: 62c8aec52fd200af8f2c3a7cd19c9a589c5353bca7bd6ed6ca08dbc990b381af
4
+ data.tar.gz: 0bae864b6bf77b4f5200f2671bbed65ff8fea1f22fbbaa9985e088d4be82d767
5
5
  SHA512:
6
- metadata.gz: b307adb81a4eac8b0cebca6778eeb060af5013a6b008f414085501e5cb7c8bb9fc32fbd4770ac8e458bf5a4f919d61b022bc5bc129199b3ae7b2f562f85f4b6a
7
- data.tar.gz: 692efe5f507fd04dec768862eb9277e2a39af3b8eea182b2e8f1ca09e0f0910ab7080dd07f6d7f5605a673cab0feab7aa1194032b54ad2c550e42b794bc44965
6
+ metadata.gz: 6fa6cabde451cec1eda5600f29131cbe78b7f0e66742bed84257e83d0b45418fc300452b24147a9bb4f319710c87da02ac541c17312e17993e1ea636c09b52c6
7
+ data.tar.gz: 4ee9dd287f81d027e0538fa94c959ffca9e560274a7d558196f163e7569f9ea7eae996857c55f5b0453a3a876863fa5b384171d368907c1c1c53ec30f72366ea
@@ -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,51 @@
1
1
  # Changelog
2
2
 
3
+ ## Unreleased
4
+ * N/A
5
+
6
+ ## 0.1.0-alpha.3
7
+ * [FEAT] Added Vernier profiling support with `profile if:` conditional interface and `Axn.config.profiling` configuration
8
+ * [FEAT] Extended model validation to support custom finder methods with `expects :user, model: { klass: User, finder: :find }` syntax
9
+ * [BREAKING] Removed `#try` method
10
+ * [BREAKING] Removed `Axn()` method sugar (use `Axn::Factory.build` directly)
11
+ * [BREAKING] Renamed `Action::Configuration` + `Action.config` -> `Axn::Configuration` + `Axn.config`
12
+ * [BREAKING] Move `Axn::Util` to `Axn::Internal::Logging`
13
+ * [BREAKING] !! Move all `Action` to `Axn` (notably `include Action` is now `include Axn`)
14
+ * [FEAT] Continues to support plain ruby usage, but when used alongside Rails now includes a Rails Engine integrate automatically (e.g. providing generators).
15
+ * Added Rails generator `rails generate axn Some::Action::Name foo bar` to create action classes with expectations
16
+ * Autoload actions from `app/actions` (add config.rails.app_actions_autoload_namespace to allow setting custom namespace)
17
+ * [INTERNAL] Clearer hooks for supporting additional background providers in the future
18
+ * [BREAKING] spec_helpers: removed rarely used `build_axn`; renamed existing `build_action` -> `build_axn`
19
+ * [FEAT] `Axn::Factory.build` can receive a callable OR a block
20
+ * [FEAT] Added `#finalized?` method to `Axn::Result` to check if result has completed execution
21
+ * [FEAT] Added `type: :params` validation option for `expects`/`exposes` that accepts Hash or ActionController::Parameters (Rails-compatible)
22
+ * [FEAT] Allow validations to access instance methods (e.g. `inclusion: { in: :some_method }`)
23
+ * [FEAT] Allow message `prefix` to invoke callables/method name symbols the same way e.g. `if` does
24
+ * [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)
25
+ * [FEAT] Support `sensitive: true` on subfields (with `on:`)
26
+ * [FEAT] Support `preprocess` on subfields (with `on:`)
27
+ * [FEAT] Support `default` on subfields (with `on:`)
28
+ * [FEAT] Added `#done!` method for early completion with success result
29
+ * [FEAT] Extended `#fail!` and `#done!` methods to accept keyword arguments for exposing data before halting execution
30
+ * [INTERAL] Renamed `Axn::Enqueueable` to `Axn::Async`
31
+ * [BREAKING] Replaced `.enqueue` (only supported sidekiq) with `.call_async` (via a configurable registry of backgrounding libraries)
32
+ * [FEAT] attachable now creates a foo_async to call call_async
33
+ * `type` validator is not still applied to the blank value when allow_blank is true (`type: Hash` will no longer accept `false` or `""`)
34
+ * [FEAT] `expects`/`exposes` now prefers new `optional: true` over allow_blank for simplicity
35
+ * [FEAT] `Axn::Result` now supports Ruby 3's pattern matching feature
36
+ * [FEAT] Extended attachable functionality: added `mount_axn_method` for creating class methods that return values directly instead of wrapped in `Axn::Result`
37
+ * [Internal] Replaced `Axn::Attachable` with `Axn::Mountable` - complete refactor of action mounting system
38
+ * [BREAKING] `#axn` → `#mount_axn` for method mounting
39
+ * [BREAKING] `#axn_method` → `#mount_axn_method` for direct method mounting
40
+ * [NEW] `enqueue_all_via` - Mount batch enqueueing functionality for background job processing
41
+ * [FEAT] Enhanced async execution with job scheduling support
42
+ * [NEW] Support for scheduled async jobs via `_async` parameter with `wait_until:` and `wait:` options
43
+ * [NEW] `enqueue` shortcut methods for all mounted actions
44
+
3
45
  ## 0.1.0-alpha.2.8.1
4
46
  * [BUGFIX] Fixed symbol callback and message handlers not working in inherited classes due to private method visibility issues
5
47
  * [BUGFIX] `default_error` and `default_success` are now properly available for before hooks
48
+ * [FEAT] Support scheduling async jobs (via new `_async` key)
6
49
 
7
50
  ## 0.1.0-alpha.2.8
8
51
  * [FEAT] Custom RuboCop cop `Axn/UncheckedResult` to enforce proper result handling in Actions with configurable nested/non-nested checking
data/Rakefile CHANGED
@@ -10,10 +10,17 @@ RSpec::Core::RakeTask.new(:spec_rubocop) do |task|
10
10
  task.pattern = "spec_rubocop/**/*_spec.rb"
11
11
  end
12
12
 
13
+ # Rails specs (separate from main specs to avoid loading Rails unnecessarily)
14
+ task :spec_rails do
15
+ Dir.chdir("spec_rails/dummy_app") do
16
+ sh "BUNDLE_GEMFILE=Gemfile bundle exec rspec spec/"
17
+ end
18
+ end
19
+
13
20
  require "rubocop/rake_task"
14
21
 
15
22
  # RuboCop with Axn custom cops (targeting examples/rubocop directory)
16
- task :rubocop_axn do
23
+ task :rubocop_examples do
17
24
  sh "bundle exec rubocop --require axn/rubocop examples/rubocop/ || true"
18
25
  end
19
26
 
@@ -21,4 +28,7 @@ end
21
28
  RuboCop::RakeTask.new
22
29
 
23
30
  task default: %i[spec rubocop]
24
- task all_specs: %i[spec spec_rubocop]
31
+ task rails_specs: %i[spec_rails]
32
+ task rubocop_specs: %i[spec_rubocop]
33
+ task all_specs: %i[spec spec_rubocop spec_rails]
34
+ task specs: %i[all_specs]
@@ -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,7 +37,8 @@ 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' },
40
42
  ]
41
43
  },
42
44
  {
@@ -45,6 +47,7 @@ export default defineConfig({
45
47
  { text: 'Memoization', link: '/recipes/memoization' },
46
48
  { text: 'Validating User Input', link: '/recipes/validating-user-input' },
47
49
  { text: 'Testing Actions', link: '/recipes/testing' },
50
+ { text: 'RuboCop Integration', link: '/recipes/rubocop-integration' },
48
51
  ]
49
52
  },
50
53
  {
@@ -55,10 +58,12 @@ export default defineConfig({
55
58
  ]
56
59
  },
57
60
  {
58
- text: 'Additional Notes',
61
+ text: 'Advanced',
59
62
  items: [
60
- { text: 'ROUGH NOTES', link: '/advanced/rough' },
63
+ { text: 'Profiling', link: '/advanced/profiling' },
61
64
  { text: 'Conventions', link: '/advanced/conventions' },
65
+ { text: 'Mountable', link: '/advanced/mountable' },
66
+ { text: 'ROUGH NOTES', link: '/advanced/rough' },
62
67
  ]
63
68
  },
64
69
  ],
@@ -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
  ```