assistant 1.0.0.rc1 → 1.0.0
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/.github/workflows/docs.yml +9 -19
- data/.gitignore +0 -3
- data/CHANGELOG.md +8 -31
- data/Gemfile +6 -9
- data/Gemfile.lock +3 -104
- data/README.md +8 -6
- data/Rakefile +17 -11
- data/Steepfile +18 -3
- data/assistant.gemspec +1 -1
- data/docs/.nojekyll +0 -0
- data/docs/404.html +292 -0
- data/docs/_media/apple-touch-icon.png +0 -0
- data/docs/_media/favicon.png +0 -0
- data/docs/_media/home-logo.svg +8 -0
- data/docs/_media/repo-card.png +0 -0
- data/docs/_sidebar.md +29 -0
- data/docs/api-reference.md +22 -11
- data/docs/changelog.md +0 -5
- data/docs/deprecations.md +4 -9
- data/docs/examples/{index.md → README.md} +7 -17
- data/docs/examples/cli-handler.md +39 -13
- data/docs/examples/composing-services.md +45 -13
- data/docs/examples/execute-callbacks.md +49 -13
- data/docs/examples/instrumentation-notifier.md +48 -13
- data/docs/examples/rails-service.md +40 -13
- data/docs/examples/rbs-generator.md +90 -13
- data/docs/examples/sidekiq-worker.md +33 -13
- data/docs/getting-started.md +11 -5
- data/docs/guides/{index.md → README.md} +1 -8
- data/docs/guides/composing-services.md +6 -12
- data/docs/guides/inputs.md +6 -12
- data/docs/guides/logging-and-results.md +9 -15
- data/docs/guides/rbs-and-types.md +151 -13
- data/docs/guides/validation.md +29 -11
- data/docs/index.html +291 -0
- data/docs/index.md +67 -24
- data/docs/roadmap.md +8 -13
- data/lib/assistant/version.rb +1 -1
- metadata +12 -5
- data/_config.yml +0 -87
|
@@ -1,17 +1,53 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: Execute callbacks
|
|
3
|
-
parent: Examples
|
|
4
|
-
nav_order: 5
|
|
5
|
-
---
|
|
6
|
-
|
|
7
1
|
# Execute callbacks
|
|
8
2
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
3
|
+
`before_execute` / `after_execute` / `around_execute` fire around the
|
|
4
|
+
`#execute` body — see the [Composing services guide](../guides/composing-services.md#execute-callbacks)
|
|
5
|
+
for the full contract.
|
|
6
|
+
|
|
7
|
+
An audit logger and a wall-time wrapper, combined:
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
class AuditedService < Assistant::Service
|
|
11
|
+
input :user_id, type: Integer, required: true
|
|
12
|
+
|
|
13
|
+
before_execute do
|
|
14
|
+
log_item_info(source: :audit, detail: :start, message: "user=#{user_id}")
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
after_execute do |_result|
|
|
18
|
+
log_item_info(source: :audit, detail: :finish, message: "user=#{user_id}")
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
around_execute do |&blk|
|
|
22
|
+
started = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
23
|
+
inner = blk.call
|
|
24
|
+
elapsed_ms = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - started) * 1000).round(1)
|
|
25
|
+
log_item_info(source: :audit, detail: :timing, message: "#{elapsed_ms}ms")
|
|
26
|
+
inner
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def execute
|
|
30
|
+
# ... business logic ...
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Failure semantics:
|
|
12
36
|
|
|
13
|
-
`before_execute`
|
|
37
|
+
* `before_execute` callbacks run **before** `#execute`. Logging an
|
|
38
|
+
error from a `before_execute` does **not** prevent `#execute` from
|
|
39
|
+
running — it just becomes part of the result hash. Use the
|
|
40
|
+
declarative `valid_*_*?` family or `#validate` when you need to
|
|
41
|
+
short-circuit.
|
|
42
|
+
* `around_execute` blocks receive the inner continuation as the block
|
|
43
|
+
argument (`&blk`) and **must** call it exactly once. Skipping
|
|
44
|
+
`blk.call` silently drops the inner result.
|
|
45
|
+
* `after_execute` blocks receive the `#execute` return value as their
|
|
46
|
+
single positional arg.
|
|
47
|
+
* If a hook itself raises, `Assistant::Service` rescues the
|
|
48
|
+
`StandardError` and logs it as
|
|
49
|
+
`source: :hook, detail: :before_execute|:around_execute|:after_execute`
|
|
50
|
+
— execution of subsequent hooks continues.
|
|
14
51
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
prose stays in lockstep with the code.
|
|
52
|
+
> Source: [`examples/execute_callbacks/`](https://github.com/ramongr/assistant/tree/main/examples/execute_callbacks) ·
|
|
53
|
+
> Test: [`test/examples/execute_callbacks_example_test.rb`](https://github.com/ramongr/assistant/blob/main/test/examples/execute_callbacks_example_test.rb)
|
|
@@ -1,17 +1,52 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: Instrumentation notifier
|
|
3
|
-
parent: Examples
|
|
4
|
-
nav_order: 6
|
|
5
|
-
---
|
|
6
|
-
|
|
7
1
|
# Instrumentation notifier
|
|
8
2
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
3
|
+
`Assistant.notifier=` accepts any object responding to `#call(event,
|
|
4
|
+
payload)`. Set it once at boot to wire every service into your
|
|
5
|
+
existing instrumentation pipeline — see the [Composing services
|
|
6
|
+
guide](../guides/composing-services.md#instrumentation-notifier)
|
|
7
|
+
for the contract and the
|
|
8
|
+
[API reference](../api-reference.md#instrumentation-notifier) for the
|
|
9
|
+
event list.
|
|
10
|
+
|
|
11
|
+
Wiring it to an `ActiveSupport::Notifications`-shaped sink:
|
|
12
|
+
|
|
13
|
+
```ruby
|
|
14
|
+
Assistant.notifier = ->(event, payload) {
|
|
15
|
+
ActiveSupport::Notifications.instrument("assistant.#{event}", payload)
|
|
16
|
+
}
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
A fake sink for tests:
|
|
20
|
+
|
|
21
|
+
```ruby
|
|
22
|
+
events = []
|
|
23
|
+
Assistant.notifier = ->(event, payload) { events << [event, payload] }
|
|
24
|
+
|
|
25
|
+
CreateUser.run(email: 'a@b.com', name: 'Alice')
|
|
26
|
+
|
|
27
|
+
events.map(&:first)
|
|
28
|
+
# => [:service_started, :service_validated, :service_executed]
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
A failure path emits `:service_failed` instead of `:service_executed`:
|
|
32
|
+
|
|
33
|
+
```ruby
|
|
34
|
+
events.clear
|
|
35
|
+
CreateUser.run(email: nil, name: 'Alice')
|
|
36
|
+
|
|
37
|
+
events.map(&:first)
|
|
38
|
+
# => [:service_started, :service_validated, :service_failed]
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Payload shape for every event is exactly two keys: `service_class:`
|
|
42
|
+
(the `Assistant::Service` subclass) and `duration_s:` (a `Float`
|
|
43
|
+
seconds since `#run` started). The event set and its payload contract
|
|
44
|
+
are **Frozen** for 1.0 — see the
|
|
45
|
+
[full payload table](../api-reference.md#instrumentation-notifier).
|
|
12
46
|
|
|
13
|
-
|
|
47
|
+
> **Warning** — Notifier callables are rescued from `StandardError`;
|
|
48
|
+
> any exception they raise is `warn`-ed but doesn't fail the service.
|
|
49
|
+
> Don't put control flow in the notifier.
|
|
14
50
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
prose stays in lockstep with the code.
|
|
51
|
+
> Source: [`examples/instrumentation_notifier/`](https://github.com/ramongr/assistant/tree/main/examples/instrumentation_notifier) ·
|
|
52
|
+
> Test: [`test/examples/instrumentation_notifier_example_test.rb`](https://github.com/ramongr/assistant/blob/main/test/examples/instrumentation_notifier_example_test.rb)
|
|
@@ -1,17 +1,44 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: Rails service
|
|
3
|
-
parent: Examples
|
|
4
|
-
nav_order: 1
|
|
5
|
-
---
|
|
6
|
-
|
|
7
1
|
# Rails service
|
|
8
2
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
3
|
+
A Rails-shaped controller that calls a service and pattern-matches on
|
|
4
|
+
the result hash:
|
|
5
|
+
|
|
6
|
+
```ruby
|
|
7
|
+
class UsersController < ApplicationController
|
|
8
|
+
def create
|
|
9
|
+
case CreateUser.run(**user_params.to_h.symbolize_keys)
|
|
10
|
+
in { result: user, status: :ok }
|
|
11
|
+
render json: user, status: :created
|
|
12
|
+
in { result: user, status: :with_warnings, warnings: }
|
|
13
|
+
Rails.logger.warn(warnings.map(&:item))
|
|
14
|
+
render json: user, status: :created
|
|
15
|
+
in { errors:, status: :with_errors }
|
|
16
|
+
render json: { errors: errors.map(&:item) },
|
|
17
|
+
status: :unprocessable_entity
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def user_params
|
|
24
|
+
params.require(:user).permit(:email, :name)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Notes:
|
|
30
|
+
|
|
31
|
+
* `Service.run` never raises for application-level failures — it always
|
|
32
|
+
returns a hash. Use `rescue` only for true exceptions (network,
|
|
33
|
+
database, etc.), and let `:with_errors` carry the validation /
|
|
34
|
+
business-rule failures.
|
|
35
|
+
* `LogItem#item` returns a `Hash{Symbol => Object}` that's safe to
|
|
36
|
+
pass straight to `render json:`.
|
|
12
37
|
|
|
13
|
-
|
|
38
|
+
See the [Getting started guide](../getting-started.md) for the
|
|
39
|
+
result-shape contract and the
|
|
40
|
+
[API reference](../api-reference.md#result-shape) for the full status
|
|
41
|
+
enum.
|
|
14
42
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
prose stays in lockstep with the code.
|
|
43
|
+
> Source: [`examples/rails_service/`](https://github.com/ramongr/assistant/tree/main/examples/rails_service) ·
|
|
44
|
+
> Test: [`test/examples/rails_service_example_test.rb`](https://github.com/ramongr/assistant/blob/main/test/examples/rails_service_example_test.rb)
|
|
@@ -1,17 +1,94 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: RBS generator
|
|
3
|
-
parent: Examples
|
|
4
|
-
nav_order: 7
|
|
5
|
-
---
|
|
6
|
-
|
|
7
1
|
# RBS generator
|
|
8
2
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
3
|
+
`bin/assistant-rbs` (installed as the `assistant-rbs` executable when
|
|
4
|
+
the gem is added to a `Gemfile`) scans Ruby files for
|
|
5
|
+
`Assistant::Service` subclasses and emits per-class RBS sidecar
|
|
6
|
+
signatures into a target directory. See the
|
|
7
|
+
[RBS and types guide](../guides/rbs-and-types.md) for the design
|
|
8
|
+
rationale and Steep setup details.
|
|
9
|
+
|
|
10
|
+
Given a service like:
|
|
11
|
+
|
|
12
|
+
```ruby
|
|
13
|
+
# examples/rbs_generator/create_user.rb
|
|
14
|
+
module RbsGeneratorExample
|
|
15
|
+
class CreateUser < Assistant::Service
|
|
16
|
+
input :email, type: String, required: true
|
|
17
|
+
input :name, type: String, required: true
|
|
18
|
+
input :role, type: [String, Symbol], allow_nil: true, default: nil
|
|
19
|
+
|
|
20
|
+
def validate
|
|
21
|
+
return if email.include?('@')
|
|
22
|
+
|
|
23
|
+
log_item_error(source: :validate, detail: :email, message: 'must contain @')
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def execute
|
|
27
|
+
{ email:, name:, role: role || :member }
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Run the generator against that file:
|
|
34
|
+
|
|
35
|
+
```text
|
|
36
|
+
$ bundle exec exe/assistant-rbs examples/rbs_generator/create_user.rb \
|
|
37
|
+
--output examples/rbs_generator/sig
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
The committed output lives at
|
|
41
|
+
`examples/rbs_generator/sig/rbs_generator_example/create_user.rbs`:
|
|
42
|
+
|
|
43
|
+
```rbs
|
|
44
|
+
# Generated by assistant-rbs; do not edit.
|
|
45
|
+
|
|
46
|
+
module RbsGeneratorExample
|
|
47
|
+
class CreateUser < Assistant::Service
|
|
48
|
+
|
|
49
|
+
def email: () -> String
|
|
50
|
+
def email?: () -> bool
|
|
51
|
+
def name: () -> String
|
|
52
|
+
def name?: () -> bool
|
|
53
|
+
def role: () -> (String | Symbol)?
|
|
54
|
+
def role?: () -> bool
|
|
55
|
+
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Steep can now type-check call sites that read
|
|
61
|
+
`RbsGeneratorExample::CreateUser#email`, `#name`, and `#role`, even though
|
|
62
|
+
those methods are defined via `define_method` at class-load time:
|
|
63
|
+
|
|
64
|
+
```ruby
|
|
65
|
+
# examples/rbs_generator/type_probe.rb
|
|
66
|
+
service = RbsGeneratorExample::CreateUser.new(email: 'ada@example.com', name: 'Ada', role: :admin)
|
|
67
|
+
|
|
68
|
+
service.email.upcase
|
|
69
|
+
service.name.length
|
|
70
|
+
service.role&.to_s
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Run the example-local check with:
|
|
74
|
+
|
|
75
|
+
```sh
|
|
76
|
+
cd examples/rbs_generator
|
|
77
|
+
bundle exec steep check --steepfile Steepfile --jobs=1
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Notes:
|
|
12
81
|
|
|
13
|
-
|
|
82
|
+
* `--output sig` writes alongside any existing `sig/` tree; the
|
|
83
|
+
generator only owns files it tagged with its `# Generated by
|
|
84
|
+
assistant-rbs; do not edit.` marker — hand-written sidecars are left
|
|
85
|
+
untouched.
|
|
86
|
+
* Re-run after each `Service.input` change. The output is fully
|
|
87
|
+
derived from the live constant graph, so CI can compare generated
|
|
88
|
+
output against committed sidecars to catch drift.
|
|
89
|
+
* The generator declares input getters and presence predicates only.
|
|
90
|
+
Hand-write additional signatures for your own domain methods if your
|
|
91
|
+
call sites need them.
|
|
14
92
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
prose stays in lockstep with the code.
|
|
93
|
+
> Source: [`examples/rbs_generator/`](https://github.com/ramongr/assistant/tree/main/examples/rbs_generator) ·
|
|
94
|
+
> Test: [`test/examples/rbs_generator_example_test.rb`](https://github.com/ramongr/assistant/blob/main/test/examples/rbs_generator_example_test.rb)
|
|
@@ -1,17 +1,37 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: Sidekiq worker
|
|
3
|
-
parent: Examples
|
|
4
|
-
nav_order: 3
|
|
5
|
-
---
|
|
6
|
-
|
|
7
1
|
# Sidekiq worker
|
|
8
2
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
3
|
+
A Sidekiq worker that runs a service idempotently and routes warnings
|
|
4
|
+
and errors to separate sinks:
|
|
5
|
+
|
|
6
|
+
```ruby
|
|
7
|
+
class CreateUserWorker
|
|
8
|
+
include Sidekiq::Worker
|
|
9
|
+
sidekiq_options retry: 5, queue: :default
|
|
10
|
+
|
|
11
|
+
def perform(user_attrs)
|
|
12
|
+
case CreateUser.run(**user_attrs.transform_keys(&:to_sym))
|
|
13
|
+
in { result:, status: :ok }
|
|
14
|
+
# Done. Sidekiq sees no exception, no retry.
|
|
15
|
+
in { result:, status: :with_warnings, warnings: }
|
|
16
|
+
WarningsSink.publish(worker: self.class.name, items: warnings.map(&:item))
|
|
17
|
+
in { errors:, status: :with_errors }
|
|
18
|
+
# Permanent business-rule failure: don't retry, surface to ops.
|
|
19
|
+
ErrorsSink.publish(worker: self.class.name, items: errors.map(&:item))
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Notes:
|
|
12
26
|
|
|
13
|
-
|
|
27
|
+
* The worker **never re-raises** for `:with_errors`. Validation /
|
|
28
|
+
business-rule failures are not transient and shouldn't burn Sidekiq
|
|
29
|
+
retries. Let true exceptions (network, database) propagate.
|
|
30
|
+
* `LogItem#item` returns plain symbols/strings, which serialize
|
|
31
|
+
cleanly to JSON / your APM tool.
|
|
32
|
+
* For idempotency, keep `CreateUser` purely declarative: read the
|
|
33
|
+
caller's identifier, look up existing state, no-op when already
|
|
34
|
+
satisfied.
|
|
14
35
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
prose stays in lockstep with the code.
|
|
36
|
+
> Source: [`examples/sidekiq_worker/`](https://github.com/ramongr/assistant/tree/main/examples/sidekiq_worker) ·
|
|
37
|
+
> Test: [`test/examples/sidekiq_worker_example_test.rb`](https://github.com/ramongr/assistant/blob/main/test/examples/sidekiq_worker_example_test.rb)
|
data/docs/getting-started.md
CHANGED
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: Getting started
|
|
3
|
-
nav_order: 1
|
|
4
|
-
---
|
|
5
|
-
|
|
6
1
|
<!-- markdownlint-disable MD013 MD024 -->
|
|
7
2
|
# Getting started
|
|
8
3
|
|
|
@@ -120,6 +115,17 @@ The `:status` value is exhaustively one of `:ok`, `:with_warnings`,
|
|
|
120
115
|
`:with_errors`. No new status values can be introduced in 1.x without a
|
|
121
116
|
deprecation cycle.
|
|
122
117
|
|
|
118
|
+
At a glance, that decision is just:
|
|
119
|
+
|
|
120
|
+
```mermaid
|
|
121
|
+
flowchart LR
|
|
122
|
+
Run([Service.run]) --> Errors{errors logged?}
|
|
123
|
+
Errors -- Yes --> WithErrors[":with_errors<br/>(result is nil)"]
|
|
124
|
+
Errors -- No --> Warnings{warnings logged?}
|
|
125
|
+
Warnings -- Yes --> WithWarnings[":with_warnings<br/>(result is set)"]
|
|
126
|
+
Warnings -- No --> Ok[":ok<br/>(result is set)"]
|
|
127
|
+
```
|
|
128
|
+
|
|
123
129
|
## What's next
|
|
124
130
|
|
|
125
131
|
- The same example, but with optional / multi-type inputs and a
|
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: Guides
|
|
3
|
-
nav_order: 2
|
|
4
|
-
has_children: true
|
|
5
|
-
permalink: /guides/
|
|
6
|
-
---
|
|
7
|
-
|
|
8
1
|
# Guides
|
|
9
2
|
|
|
10
3
|
Topic-focused walkthroughs of every part of the Assistant DSL.
|
|
@@ -16,7 +9,7 @@ tests under `test/docs/<guide>_examples_test.rb`, and ends with a
|
|
|
16
9
|
`default:`, `allow_nil:`, `optional:`, `if:` conditional requirement,
|
|
17
10
|
and the `assistant-rbs` Steep recipe.
|
|
18
11
|
- [Validation](./validation.md) — auto-checks, `#validate`, warnings
|
|
19
|
-
vs errors, conditional requirements, strict `LogItem.new
|
|
12
|
+
vs errors, conditional requirements, strict `LogItem.new`.
|
|
20
13
|
- [Logging and results](./logging-and-results.md) — `LogItem`,
|
|
21
14
|
levels, `log_item_*` shorthands, `merge_logs`, the result hash.
|
|
22
15
|
- [Composing services](./composing-services.md) — `call_service`,
|
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: Composing services
|
|
3
|
-
parent: Guides
|
|
4
|
-
nav_order: 4
|
|
5
|
-
---
|
|
6
|
-
|
|
7
1
|
<!-- markdownlint-disable MD013 MD024 -->
|
|
8
2
|
# Composing services
|
|
9
3
|
|
|
@@ -17,8 +11,8 @@ nav_order: 4
|
|
|
17
11
|
> collaborator.
|
|
18
12
|
|
|
19
13
|
This guide covers the four composition surfaces shipped in 1.0:
|
|
20
|
-
service-to-service calls
|
|
21
|
-
|
|
14
|
+
service-to-service calls, execute callbacks, the instrumentation
|
|
15
|
+
notifier, and the input snapshot.
|
|
22
16
|
|
|
23
17
|
## `#call_service`: nest one service inside another
|
|
24
18
|
|
|
@@ -67,7 +61,7 @@ Notes:
|
|
|
67
61
|
- `call_service` always calls `inner.run`, so calling it twice would
|
|
68
62
|
re-execute the inner service.
|
|
69
63
|
|
|
70
|
-
## Execute callbacks
|
|
64
|
+
## Execute callbacks
|
|
71
65
|
|
|
72
66
|
Three class-level DSL methods register callbacks around `#execute`.
|
|
73
67
|
Hooks are evaluated in the context of the service instance.
|
|
@@ -112,7 +106,7 @@ Behavior:
|
|
|
112
106
|
- **Missing block:** registering a hook without a block raises
|
|
113
107
|
`ArgumentError` at class-definition time.
|
|
114
108
|
|
|
115
|
-
## Instrumentation notifier
|
|
109
|
+
## Instrumentation notifier
|
|
116
110
|
|
|
117
111
|
Assign a callable to `Assistant.notifier =` to receive a fixed set
|
|
118
112
|
of events for **every** service execution:
|
|
@@ -153,7 +147,7 @@ To disable instrumentation entirely, restore the default no-op:
|
|
|
153
147
|
Assistant.notifier = Assistant::DEFAULT_NOTIFIER
|
|
154
148
|
```
|
|
155
149
|
|
|
156
|
-
## `#input_snapshot`
|
|
150
|
+
## `#input_snapshot`
|
|
157
151
|
|
|
158
152
|
`#input_snapshot` returns a read-only `Data` view of the service's
|
|
159
153
|
declared inputs (post-`default:` / post-`allow_nil:`). It's the
|
|
@@ -218,5 +212,5 @@ Notes:
|
|
|
218
212
|
requirements.
|
|
219
213
|
- [Logging and results](./logging-and-results.md) — what
|
|
220
214
|
`merge_logs(logs:)` does, the result hash shape.
|
|
221
|
-
- [API reference](../api-reference.md#
|
|
215
|
+
- [API reference](../api-reference.md#assistant-service) for the
|
|
222
216
|
full callback / call_service / notifier surfaces.
|
data/docs/guides/inputs.md
CHANGED
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: Inputs
|
|
3
|
-
parent: Guides
|
|
4
|
-
nav_order: 1
|
|
5
|
-
---
|
|
6
|
-
|
|
7
1
|
<!-- markdownlint-disable MD013 MD024 -->
|
|
8
2
|
# Inputs
|
|
9
3
|
|
|
@@ -40,7 +34,7 @@ Three things to notice:
|
|
|
40
34
|
1. **`input` and `inputs` take a leading positional name** (`:email`,
|
|
41
35
|
`%i[street city]`). Every other DSL option is a keyword argument.
|
|
42
36
|
This is the only place in the gem where a positional argument
|
|
43
|
-
survives the
|
|
37
|
+
survives the keyword-only DSL — see
|
|
44
38
|
[`api-reference.md`](../api-reference.md#class-methods).
|
|
45
39
|
2. **The constructor is keyword-only.** You call
|
|
46
40
|
`CreateUser.run(email: 'a@b.com', name: 'Alice')`, never
|
|
@@ -78,7 +72,7 @@ TouchEmail.run(email: 42)
|
|
|
78
72
|
# message: "Service argument with name email is not a String but Integer">] }
|
|
79
73
|
```
|
|
80
74
|
|
|
81
|
-
### Multi-type inputs
|
|
75
|
+
### Multi-type inputs
|
|
82
76
|
|
|
83
77
|
Pass an array of classes when more than one is acceptable:
|
|
84
78
|
|
|
@@ -126,7 +120,7 @@ The deprecated 0.x name `#valid_require_<name>?` still works in 1.x —
|
|
|
126
120
|
calls emit a one-time `Kernel.warn` per call site and delegate to the
|
|
127
121
|
canonical predicate. See [`docs/deprecations.md`](../deprecations.md).
|
|
128
122
|
|
|
129
|
-
## `default:`
|
|
123
|
+
## `default:`
|
|
130
124
|
|
|
131
125
|
Provide a fallback when the caller omits an input. Pass a callable
|
|
132
126
|
(method, lambda, or proc) to compute the default lazily — `assistant`
|
|
@@ -155,7 +149,7 @@ input :token, type: String, default: -> { SecureRandom.uuid }
|
|
|
155
149
|
A `default:` provider that takes arguments raises `ArgumentError` at
|
|
156
150
|
class-definition time.
|
|
157
151
|
|
|
158
|
-
## `allow_nil:`
|
|
152
|
+
## `allow_nil:`
|
|
159
153
|
|
|
160
154
|
By default, `nil` for a typed input logs a type-mismatch error.
|
|
161
155
|
`allow_nil: true` makes `nil` a legal value:
|
|
@@ -177,7 +171,7 @@ TouchAge.run(age: 30).fetch(:result) # => 30
|
|
|
177
171
|
Combine with `default:` to express "optional integer that defaults to
|
|
178
172
|
nil and may be set to nil explicitly".
|
|
179
173
|
|
|
180
|
-
## `optional: true`
|
|
174
|
+
## `optional: true`
|
|
181
175
|
|
|
182
176
|
`optional: true` is a shorthand for "skip the presence check entirely;
|
|
183
177
|
do not generate `#valid_required_name?`". It is mutually exclusive
|
|
@@ -278,7 +272,7 @@ generic `.rbs` for `Service` can't know that your `CreateUser#email`
|
|
|
278
272
|
returns `String`. That's R1 in
|
|
279
273
|
[`docs/v1/05-quality-and-tooling.md`](https://github.com/ramongr/assistant/blob/main/docs/v1/05-quality-and-tooling.md).
|
|
280
274
|
|
|
281
|
-
The bundled `assistant-rbs` CLI
|
|
275
|
+
The bundled `assistant-rbs` CLI closes the gap by emitting
|
|
282
276
|
per-class `.rbs` files. Run it once after editing your services:
|
|
283
277
|
|
|
284
278
|
```sh
|
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: Logging and results
|
|
3
|
-
parent: Guides
|
|
4
|
-
nav_order: 3
|
|
5
|
-
---
|
|
6
|
-
|
|
7
1
|
<!-- markdownlint-disable MD013 MD024 -->
|
|
8
2
|
# Logging and results
|
|
9
3
|
|
|
@@ -12,7 +6,7 @@ nav_order: 3
|
|
|
12
6
|
> add entries, `#logs` / `#infos` / `#warnings` / `#errors` to read
|
|
13
7
|
> them, and the result hash returned by `.run` to consume the
|
|
14
8
|
> service from outside. `LogItem.new` raises `ArgumentError` for
|
|
15
|
-
> invalid attributes
|
|
9
|
+
> invalid attributes — prefer the helpers.
|
|
16
10
|
|
|
17
11
|
This guide covers the data model, the writer helpers, the reader
|
|
18
12
|
predicates, and the shape of the result hash. See the
|
|
@@ -32,7 +26,7 @@ fields:
|
|
|
32
26
|
| `message` | `String` | Human-readable text. |
|
|
33
27
|
| `trace` | `Array<String>` or `nil` | Optional backtrace captured at construction. |
|
|
34
28
|
|
|
35
|
-
Constraints (enforced strictly in 1.0
|
|
29
|
+
Constraints (enforced strictly in 1.0):
|
|
36
30
|
|
|
37
31
|
- `source != detail`.
|
|
38
32
|
- `source` and `detail` must each be non-empty.
|
|
@@ -46,7 +40,7 @@ Assistant::LogItem::VALID_LEVELS
|
|
|
46
40
|
|
|
47
41
|
## Writing log entries
|
|
48
42
|
|
|
49
|
-
The three shorthand helpers
|
|
43
|
+
The three shorthand helpers are the recommended call sites
|
|
50
44
|
inside `#validate` and `#execute`:
|
|
51
45
|
|
|
52
46
|
```ruby
|
|
@@ -162,7 +156,7 @@ def execute
|
|
|
162
156
|
end
|
|
163
157
|
```
|
|
164
158
|
|
|
165
|
-
> **
|
|
159
|
+
> **Keyword-only.** `#merge_logs` is keyword-only in 1.0. Passing positional
|
|
166
160
|
> arguments raises `ArgumentError`. The
|
|
167
161
|
> [migration guide](https://github.com/ramongr/assistant/blob/main/docs/v1/06-migration-0x-to-1.md) covers the
|
|
168
162
|
> mechanical rewrite.
|
|
@@ -181,14 +175,14 @@ service.errors.first.item
|
|
|
181
175
|
## Common pitfalls
|
|
182
176
|
|
|
183
177
|
- **Pushing onto `@logs` directly.** Don't — always go through the
|
|
184
|
-
helpers so the
|
|
178
|
+
helpers so the strict construction runs and so future
|
|
185
179
|
middleware (e.g. an instrumentation hook around `#add_log`) can
|
|
186
180
|
see the entry.
|
|
187
181
|
- **Using `LogItem.new` with `source == detail`.** Raises
|
|
188
182
|
`ArgumentError`. Pick distinct symbols.
|
|
189
183
|
- **Treating `#infos` as part of the contract.** They're for
|
|
190
184
|
introspection only; the result hash never includes them.
|
|
191
|
-
- **Calling `merge_logs(other.logs)` (positional).**
|
|
185
|
+
- **Calling `merge_logs(other.logs)` (positional).** 1.0 requires
|
|
192
186
|
the keyword form: `merge_logs(logs: other.logs)`.
|
|
193
187
|
|
|
194
188
|
## See also
|
|
@@ -197,6 +191,6 @@ service.errors.first.item
|
|
|
197
191
|
conditional checks, `#validate` mechanics.
|
|
198
192
|
- [Composing services](./composing-services.md) — how `#call_service`
|
|
199
193
|
merges inner logs into the outer timeline.
|
|
200
|
-
- [API reference: LogItem](../api-reference.md#
|
|
201
|
-
- [API reference: LogList](../api-reference.md#
|
|
202
|
-
- [Migration guide](https://github.com/ramongr/assistant/blob/main/docs/v1/06-migration-0x-to-1.md) for
|
|
194
|
+
- [API reference: LogItem](../api-reference.md#assistant-logitem).
|
|
195
|
+
- [API reference: LogList](../api-reference.md#assistant-loglist).
|
|
196
|
+
- [Migration guide](https://github.com/ramongr/assistant/blob/main/docs/v1/06-migration-0x-to-1.md) for the 1.0 breaking changes.
|