Class: Gaskit::Operation Abstract

Inherits:
Object
  • Object
show all
Includes:
Hookable
Defined in:
lib/gaskit/operation.rb

Overview

This class is abstract.

Subclass this and define ‘#call` or `#call!` to create a new operation.

The Gaskit::Operation class defines a structured and extensible pattern for building application operations. It enforces consistent behavior across operations while supporting customization via contracts.

# Features

  • Pluggable contracts via ‘use_contract`, allowing you to define or reference a `result` class.

  • Integrated duration tracking, structured logging, and early exits.

  • Supports ‘.call` (non-raising) and `.call!` (raising) styles.

Examples:

Using a registered contract

class MyOperation < Gaskit::Operation
  use_contract :service

  def call
    # Do work
    "done"
  end
end

Overriding only part of the contract

class MyCustomOp < Gaskit::Operation
  use_contract :service, result: MyCustomResult

  def call
    exit(:unauthorized, "User not allowed") if unauthorized?
    "okay"
  end
end

Fully manual contract

class ManualOp < Gaskit::Operation
  use_contract result: MyResult, early_exit: MyExit

  def call
    do_work
  end
end

result = ManualOp.call(context: { request_id: "abc123" })

Direct Known Subclasses

Query, Service

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Hookable

#apply_after_hooks, #apply_around_hooks, #apply_before_hooks, #apply_hooks, included

Constructor Details

#initialize(raise_on_failure, context: {}) ⇒ void

Initializes a new Gaskit::Operation instance.

Parameters:

  • raise_on_failure (Boolean)

    Whether to raise exceptions on failure.

  • context (Hash) (defaults to: {})

    Context data for the operation.

Raises:



265
266
267
268
269
270
271
272
273
# File 'lib/gaskit/operation.rb', line 265

def initialize(raise_on_failure, context: {})
  @raise_on_failure = raise_on_failure
  @context = apply_context(context)
  @logger = Gaskit::Logger.new(self, context: @context)

  return unless self.class.result_class.nil?

  raise Gaskit::Error, "No result_class defined for #{self.class.name} or its ancestors."
end

Instance Attribute Details

#contextObject (readonly)

Returns the value of attribute context.



258
259
260
# File 'lib/gaskit/operation.rb', line 258

def context
  @context
end

#loggerObject (readonly)

Returns the value of attribute logger.



258
259
260
# File 'lib/gaskit/operation.rb', line 258

def logger
  @logger
end

#raise_on_failureObject (readonly)

Returns the value of attribute raise_on_failure.



258
259
260
# File 'lib/gaskit/operation.rb', line 258

def raise_on_failure
  @raise_on_failure
end

Class Method Details

.call(*args, context: {}, **kwargs, &block) ⇒ Gaskit::OperationResult

Executes the operation with soft-failure handling

Parameters:

  • args (Array)

    Positional arguments for the first step

  • context (Hash) (defaults to: {})

    Shared context across all steps

  • kwargs (Hash)

    Keyword arguments for the first step

Returns:



117
118
119
# File 'lib/gaskit/operation.rb', line 117

def call(*args, context: {}, **kwargs, &block)
  invoke(false, context, *args, **kwargs, &block)
end

.call!(*args, context: {}, **kwargs, &block) ⇒ Gaskit::OperationResult

Executes the operation with hard-failure handling (raises on unhandled errors)

Parameters:

  • args (Array)

    Positional arguments for the first step

  • context (Hash) (defaults to: {})

    Shared context across all steps

  • kwargs (Hash)

    Keyword arguments for the first step

Returns:



127
128
129
# File 'lib/gaskit/operation.rb', line 127

def call!(*args, context: {}, **kwargs, &block)
  invoke(true, context, *args, **kwargs, &block)
end

.error(key, message, code: nil) ⇒ void

This method returns an undefined value.

Declares a symbolic error and message for use with ‘exit(:key)`

Examples:

error :unauthorized, "You must be signed in", code: "AUTH-001"

Parameters:

  • key (String, Symbol)

    The key used to declare the error.

  • message (String)

    The error message.

  • code (String, nil) (defaults to: nil)

    Optional error code.

Raises:

  • (ArgumentError)


96
97
98
99
100
101
102
# File 'lib/gaskit/operation.rb', line 96

def error(key, message, code: nil)
  raise ArgumentError, "Error key must be a symbol or a string, received #{key}" unless key.is_a?(Symbol)
  raise ArgumentError, "Error message must be a string" unless message.is_a?(String)
  raise ArgumentError, "Error key :#{key} is already registered" if errors_registry.key?(key)

  errors_registry[key.to_sym] = { message: message, code: code }
end

.errors_registryvoid

This method returns an undefined value.

Returns the error registry for the operation class



107
108
109
# File 'lib/gaskit/operation.rb', line 107

def errors_registry
  @errors_registry ||= {}
end

.result_classObject



54
55
56
57
58
59
# File 'lib/gaskit/operation.rb', line 54

def result_class
  return @result_class if defined?(@result_class) && @result_class
  return superclass&.result_class if superclass.respond_to?(:result_class)

  nil
end

.use_contract(contract = nil, result: nil) ⇒ void

This method returns an undefined value.

Defines the result class for this operation. Can reference a named contract registered in ‘Gaskit::Registry`, or define one without using a registered contract.

Examples:

Use a registered contract

use_contract :service

Define a contract that has not been registered

use_contract result: CustomResult

Parameters:

  • contract (Symbol, nil) (defaults to: nil)

    A registered contract name (e.g., ‘:service`)

  • result (Class, nil) (defaults to: nil)

    A class that inherits from ‘Gaskit::BaseResult`

Raises:

  • (ArgumentError)

    if contract is not a symbol or unexpected args are passed

  • (ResultTypeError)

    if ‘result` is not a subclass of `Gaskit::BaseResult`



76
77
78
79
80
81
82
83
84
85
# File 'lib/gaskit/operation.rb', line 76

def use_contract(contract = nil, result: nil)
  if contract
    raise ArgumentError, "use_contract must be called with a symbol or keyword args" unless contract.is_a?(Symbol)

    result = Gaskit.contracts.fetch(contract)
  end

  Gaskit::ContractRegistry.verify_result_class!(result)
  @result_class = result
end

Instance Method Details

#call(*args, **kwargs) ⇒ void

This method returns an undefined value.

Executes the operation logic.

Parameters:

  • args (Array)

    Positional arguments passed.

  • kwargs (Hash)

    Keyword arguments passed.

Raises:

  • (NotImplementedError)

    Must be implemented by subclasses.



285
286
287
# File 'lib/gaskit/operation.rb', line 285

def call(*args, **kwargs)
  raise NotImplementedError, "#{self.class.name} must implement `#call`"
end

#exit(error_key, message = nil, code: nil) ⇒ Object Also known as: abort

Terminates the operation early with a symbolic key.

If the key was previously registered via ‘self.error`, it uses the declared message and code. Otherwise, it uses the key as the message.

Parameters:

  • error_key (Symbol)

    The symbolic reason for exiting.

  • message (String, nil) (defaults to: nil)

    Optional message override.

  • code (String, nil) (defaults to: nil)

    Optional error code.

Raises:

  • (OperationExit)

    always raises an instance with message and optional code



298
299
300
301
302
303
304
305
306
307
308
# File 'lib/gaskit/operation.rb', line 298

def exit(error_key, message = nil, code: nil)
  error_key = error_key.to_sym
  definition = self.class.errors_registry.fetch(error_key, nil)

  if definition
    message ||= definition[:message]
    code ||= definition[:code]
  end

  raise OperationExit.new(error_key, message, code: code)
end

#raise_on_failure?Boolean

Returns:

  • (Boolean)


275
276
277
# File 'lib/gaskit/operation.rb', line 275

def raise_on_failure?
  @raise_on_failure
end