Class: Servus::EventHandler

Inherits:
Object
  • Object
show all
Defined in:
lib/servus/event_handler.rb

Overview

Base class for event handlers that map events to service invocations.

EventHandler classes live in app/events/ and use a declarative DSL to subscribe to events and invoke services in response. Each handler subscribes to a single event via the handles method.

Examples:

Basic event handler

class UserCreatedHandler < Servus::EventHandler
  handles :user_created

  invoke SendWelcomeEmail::Service, async: true do |payload|
    { user_id: payload[:user_id] }
  end
end

See Also:

Class Attribute Summary collapse

Class Method Summary collapse

Class Attribute Details

.event_nameSymbol? (readonly)

Returns the event name this handler is subscribed to.

Returns:

  • (Symbol, nil)

    the event name or nil if not yet configured



47
48
49
# File 'lib/servus/event_handler.rb', line 47

def event_name
  @event_name
end

.payload_schemaHash? (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns the payload schema.

Returns:

  • (Hash, nil)

    the payload schema or nil if not defined



123
124
125
# File 'lib/servus/event_handler.rb', line 123

def payload_schema
  @payload_schema
end

Class Method Details

.emit(payload) ⇒ void

This method returns an undefined value.

Emits the event this handler is subscribed to.

Provides a type-safe, discoverable way to emit events from anywhere in the application (controllers, jobs, rake tasks) without creating a service.

Examples:

Emit from controller

class UsersController
  def create
    user = User.create!(params)
    UserCreatedHandler.emit({ user_id: user.id, email: user.email })
    redirect_to user
  end
end

Emit from background job

class ProcessDataJob
  def perform(data_id)
    result = process_data(data_id)
    DataProcessedHandler.emit({ data_id: data_id, status: result })
  end
end

Parameters:

  • payload (Hash)

    the event payload

Raises:

  • (RuntimeError)

    if no event configured via handles



150
151
152
153
154
155
156
# File 'lib/servus/event_handler.rb', line 150

def emit(payload)
  raise 'No event configured. Call handles :event_name first.' unless @event_name

  Servus::Support::Validator.validate_event_payload!(self, payload)

  Servus::Events::Bus.emit(@event_name, payload)
end

.handle(payload) ⇒ Array<Servus::Support::Response>

Handles an event by invoking all configured services.

Iterates through all declared invocations, evaluates conditions, maps the payload to service arguments, and invokes each service.

Examples:

UserCreatedHandler.handle({ user_id: 123, email: '[email protected]' })

Parameters:

  • payload (Hash)

    the event payload

Returns:



168
169
170
171
172
173
174
# File 'lib/servus/event_handler.rb', line 168

def handle(payload)
  invocations.map do |invocation|
    next unless should_invoke?(payload, invocation[:options])

    invoke_service(invocation, payload)
  end.compact
end

.handles(event_name) ⇒ void

This method returns an undefined value.

Declares which event this handler subscribes to.

This method registers the handler with the event bus and stores the event name for later reference. Each handler can only subscribe to one event.

Examples:

class UserCreatedHandler < Servus::EventHandler
  handles :user_created
end

Parameters:

  • event_name (Symbol)

    the name of the event to handle

Raises:

  • (RuntimeError)

    if handles is called multiple times



37
38
39
40
41
42
# File 'lib/servus/event_handler.rb', line 37

def handles(event_name)
  raise "Handler already subscribed to :#{@event_name}. Cannot subscribe to :#{event_name}" if @event_name

  @event_name = event_name
  Servus::Events::Bus.register_handler(event_name, self)
end

.invocationsArray<Hash>

Returns all service invocations declared for this handler.

Returns:

  • (Array<Hash>)

    array of invocation configurations



93
94
95
# File 'lib/servus/event_handler.rb', line 93

def invocations
  @invocations || []
end

.invoke(service_class, options = {}) {|payload| ... } ⇒ void

This method returns an undefined value.

Declares a service invocation in response to the event.

Multiple invocations can be declared for a single event. Each invocation requires a block that maps the event payload to the service's arguments.

Examples:

Basic invocation

invoke SendEmail::Service do |payload|
  { user_id: payload[:user_id], email: payload[:email] }
end

Async invocation with queue

invoke SendEmail::Service, async: true, queue: :mailers do |payload|
  { user_id: payload[:user_id] }
end

Conditional invocation

invoke GrantRewards::Service, if: ->(p) { p[:premium] } do |payload|
  { user_id: payload[:user_id] }
end

Parameters:

  • service_class (Class)

    the service class to invoke (must inherit from Servus::Base)

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

    invocation options

Options Hash (options):

  • :async (Boolean)

    invoke the service asynchronously via call_async

  • :queue (Symbol)

    the queue name for async jobs

  • :if (Proc)

    condition that must return true for invocation

  • :unless (Proc)

    condition that must return false for invocation

Yields:

  • (payload)

    block that maps event payload to service arguments

Yield Parameters:

  • payload (Hash)

    the event payload

Yield Returns:

  • (Hash)

    keyword arguments for the service's initialize method

Raises:

  • (ArgumentError)


79
80
81
82
83
84
85
86
87
88
# File 'lib/servus/event_handler.rb', line 79

def invoke(service_class, options = {}, &block)
  raise ArgumentError, 'Block required for payload mapping' unless block

  @invocations ||= []
  @invocations << {
    service_class: service_class,
    options: options,
    mapper: block
  }
end

.schema(payload: nil) ⇒ void

This method returns an undefined value.

Defines the JSON schema for validating event payloads.

Examples:

class UserCreatedHandler < Servus::EventHandler
  handles :user_created

  schema payload: {
    type: 'object',
    required: ['user_id', 'email'],
    properties: {
      user_id: { type: 'integer' },
      email: { type: 'string', format: 'email' }
    }
  }
end

Parameters:

  • payload (Hash, nil) (defaults to: nil)

    JSON schema for validating event payloads



115
116
117
# File 'lib/servus/event_handler.rb', line 115

def schema(payload: nil)
  @payload_schema = payload.with_indifferent_access if payload
end

.validate_all_handlers!void

This method returns an undefined value.

Validates that all registered handlers subscribe to events that are actually emitted by services.

Checks all handlers against all service emissions and raises an error if any handler subscribes to a non-existent event. Helps catch typos and orphaned handlers.

Respects the Servus.config.strict_event_validation setting - skips validation if false.

Examples:

Servus::EventHandler.validate_all_handlers!

Raises:



188
189
190
191
192
193
194
195
196
197
198
199
# File 'lib/servus/event_handler.rb', line 188

def validate_all_handlers!
  return unless Servus.config.strict_event_validation

  emitted_events = collect_emitted_events
  orphaned       = find_orphaned_handlers(emitted_events)

  return if orphaned.empty?

  raise Servus::Events::OrphanedHandlerError,
        "Handler(s) subscribe to non-existent events:\n" \
        "#{orphaned.map { |h| "  - #{h[:handler]} subscribes to :#{h[:event]}" }.join("\n")}"
end