Class: Assistant::Service

Inherits:
Object
  • Object
show all
Extended by:
ExecuteCallbacks, InputBuilder
Includes:
LogList
Defined in:
lib/assistant/service.rb

Overview

Core Service base class. Subclasses declare inputs via the InputBuilder DSL, optionally register before/after/around execute hooks via ExecuteCallbacks, and implement #execute.

Examples:

Minimal service

class Greet < Assistant::Service
  input :name, type: String, required: true

  def execute
    "Hello, #{name}!"
  end
end

Greet.run(name: 'Ada')
# => { result: "Hello, Ada!", status: :ok, warnings: [] }

Constant Summary

Constants included from ExecuteCallbacks

ExecuteCallbacks::HOOK_TYPES

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from ExecuteCallbacks

after_execute, after_execute_hooks, around_execute, around_execute_hooks, before_execute, before_execute_hooks, inherited

Methods included from InputBuilder::Dsl

#input

Methods included from InputBuilder::TypeValidator

#input_type_validator_meth, #type_mismatch_message_builder, #type_validator_body

Methods included from InputBuilder::RequireValidator

__reset_deprecation_warnings__, #input_require_conditional_meth, #input_require_validator_meth, warn_deprecated

Methods included from InputBuilder::Accessors

#input_checker_meth, #input_getter_meth

Methods included from InputBuilder::OptionalOption

#apply_optional_option, #process_optional_option, #validate_optional!

Methods included from InputBuilder::DefaultOption

#process_default_option, #validate_default!, #warn_on_mutable_default

Methods included from InputBuilder::Registry

#input_definitions, #register_input_definition

Methods included from LogList

#add_log, #log_item_error_initialize, #merge_logs

Constructor Details

#initialize(**args) ⇒ Service

Returns a new instance of Service.

Parameters:

  • args (Hash)

    keyword arguments matching the declared inputs. Unknown keys are accepted but excluded from #input_snapshot.



63
64
65
66
67
# File 'lib/assistant/service.rb', line 63

def initialize(**args)
  @inputs = args
  apply_input_defaults
  @logs = []
end

Instance Attribute Details

#logsArray<Assistant::LogItem> (readonly)

Public reader for the full log timeline (info + warning + error), in insertion order. See docs/v1/02-features.md M4.

Returns:



30
31
32
# File 'lib/assistant/service.rb', line 30

def logs
  @logs
end

Class Method Details

.input_snapshot_classClass

M-S4: per-subclass Data class whose members are the declared input names, in declaration order. Memoised on the subclass and transparently rebuilt if input_definitions changes (e.g. a late input :foo after the first snapshot call). Used by Service#input_snapshot; users normally never touch it.

Returns:

  • (Class)

    a Data.define subclass



52
53
54
55
56
57
58
# File 'lib/assistant/service.rb', line 52

def input_snapshot_class
  keys = input_definitions.keys
  return @input_snapshot_class if @input_snapshot_class && @input_snapshot_class_keys == keys

  @input_snapshot_class_keys = keys
  @input_snapshot_class = Data.define(*keys)
end

.runHash

Convenience: build a service with the given keyword arguments and immediately invoke #run, returning the result hash. Equivalent to new(**inputs).run.

Returns:

  • (Hash)

    the result payload — see #run



41
42
43
# File 'lib/assistant/service.rb', line 41

def run(**)
  new(**).run
end

Instance Method Details

#call_service(klass, **inputs) ⇒ Assistant::Service

M-S2: instantiate klass, run it, merge its log timeline into the current service, and return the inner service instance.

The full log timeline of the inner service (info + warning + error) is concatenated onto the outer service's @logs via merge_logs. Because the outer service's errors, warnings, and status are derived by filtering @logs, an inner error automatically downgrades the outer terminal status to :with_errors, and inner warnings surface as :with_warnings when no errors are present — without any special handling in the caller.

The returned inner instance exposes #result, #success?, #failure?, etc. so the caller can branch on the inner outcome:

call_service does not rescue exceptions raised by the inner service's #execute or by Assistant.notifier; those propagate to the caller, matching the base Service#run contract. To run an inner service that may raise, wrap the call in a begin/rescue and use add_log(level: :error, …) to record the failure.

Examples:

def execute
  other = call_service(OtherService, foo: 1)
  return if failure?
  other.result + 1
end

Parameters:

  • klass (Class<Assistant::Service>)

    the inner service class

  • inputs (Hash)

    keyword arguments forwarded to klass.new

Returns:

Raises:



155
156
157
158
159
160
161
162
163
164
# File 'lib/assistant/service.rb', line 155

def call_service(klass, **inputs)
  unless klass.is_a?(Class) && klass <= Assistant::Service
    raise ArgumentError, "call_service expects an Assistant::Service subclass, got #{klass.inspect}"
  end

  inner = klass.new(**inputs)
  inner.run
  merge_logs(logs: inner.logs)
  inner
end

#failure?Boolean

Returns true when at least one :error entry has been logged.

Returns:

  • (Boolean)

    true when at least one :error entry has been logged



111
112
113
# File 'lib/assistant/service.rb', line 111

def failure?
  errors.any?
end

#input_snapshotData

M-S4: a read-only Data view over the declared inputs of this service, post-default: / post-allow_nil:. Members are the input names declared via Service.input / Service.inputs, in declaration order; values are read from @inputs after apply_input_defaults has run, so callers see the same values the per-input getters expose.

The returned object is a Data instance, so it is structurally immutable: no member can be reassigned. Member values that are themselves mutable (e.g. an Array passed as an input) keep their normal mutability — the snapshot does not deep-freeze.

Only declared inputs appear in the snapshot. Extra keyword arguments accepted by #initialize (which live in @inputs but have no input :foo declaration) are intentionally excluded so the snapshot's shape matches the public DSL.

A declared input with no default and no caller-supplied value appears with a nil member value, mirroring the behaviour of the per-input getter.

Examples:

class Greet < Assistant::Service
  input :name, type: String, required: true
  input :loud, type: TrueClass, default: false
end

Greet.new(name: 'Ada').input_snapshot
# => #<data name="Ada", loud=false>

Returns:

  • (Data)

    read-only view over input_definitions.keys



197
198
199
200
# File 'lib/assistant/service.rb', line 197

def input_snapshot
  keys = self.class.input_definitions.keys
  self.class.input_snapshot_class.new(**keys.to_h { |name| [name, @inputs[name]] })
end

#resultObject

Memoised return value of #execute, threaded through the registered before/around/after execute hooks (M-S1).

Returns:

  • (Object)

    whatever the subclass's #execute returned



101
102
103
# File 'lib/assistant/service.rb', line 101

def result
  @result ||= run_execute_with_callbacks
end

#runHash{Symbol => Object}

Execute the validation + execute pipeline once and return the result payload. Idempotent: calling #run a second time returns an updated payload based on the memoised #result, but #execute itself runs only once (M-S1 hook chain is gated by #result's ||=).

Returns:

  • (Hash{Symbol => Object})

    either

    • { result: Object, status: :ok | :with_warnings, warnings: Array<LogItem> } on success, or
    • { errors: Array<LogItem>, result: nil, status: :with_errors } when any error has been logged before or during validation.


80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
# File 'lib/assistant/service.rb', line 80

def run
  @run_started_at = Process.clock_gettime(Process::CLOCK_MONOTONIC)
  notify(:service_started)

  validate_inputs
  validate
  notify(:service_validated)

  return failed_payload if errors.any?

  # Trigger `#execute` (through the M-S1 hook chain) eagerly so any
  # error logged by a `before_/after_/around_execute` hook influences
  # the terminal event and the payload's `:status` field.
  result
  errors.empty? ? executed_payload : failed_payload
end

#statusSymbol

Terminal status for the success path. Failure runs use :with_errors directly in the result payload (see #run).

Returns:

  • (Symbol)

    :ok when there are no warnings, otherwise :with_warnings



119
120
121
# File 'lib/assistant/service.rb', line 119

def status
  warnings.empty? ? :ok : :with_warnings
end

#success?Boolean

Returns true when no :error entries have been logged.

Returns:

  • (Boolean)

    true when no :error entries have been logged



106
107
108
# File 'lib/assistant/service.rb', line 106

def success?
  errors.empty?
end