Module: Hooks::Core::ComponentAccess

Overview

Shared module providing access to global components (logger, stats, failbot, and user-defined components)

This module provides a consistent interface for accessing global components across all plugin types, eliminating code duplication and ensuring consistent behavior throughout the application.

In addition to built-in components (log, stats, failbot), this module provides dynamic access to any user-defined components passed to Hooks.build().

Examples:

Usage in a class that needs instance methods

class MyHandler
  include Hooks::Core::ComponentAccess

  def process
    log.info("Processing request")
    stats.increment("requests.processed")
    failbot.report("Error occurred") if error?
  end
end

Usage in a class that needs class methods

class MyValidator
  extend Hooks::Core::ComponentAccess

  def self.validate
    log.info("Validating request")
    stats.increment("requests.validated")
  end
end

Using user-defined components

# Application setup
publisher = KafkaPublisher.new
email_service = EmailService.new
app = Hooks.build(
  config: "config.yaml",
  publisher: publisher,
  email_service: email_service
)

# Handler implementation
class WebhookHandler < Hooks::Plugins::Handlers::Base
  include Hooks::Core::ComponentAccess

  def call(payload:, headers:, env:, config:)
    # Use built-in components
    log.info("Processing webhook")
    stats.increment("webhooks.received")

    # Use user-defined components
    publisher.send_message(payload, topic: "webhooks")
    email_service.send_notification(payload['email'], "Webhook processed")

    { status: "success" }
  end
end

Instance Method Summary collapse

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(method_name, *args, **kwargs, &block) ⇒ Object

Dynamic method access for user-defined components

This method enables handlers to call user-defined components as methods. For example, if a user registers a ‘publisher’ component, handlers can call ‘publisher` or `publisher.some_method` directly.

The method supports multiple usage patterns:

  • Direct access: Returns the component instance for further method calls

  • Callable access: If the component responds to #call, invokes it with provided arguments

  • Method chaining: Allows fluent interface patterns with registered components

Examples:

Accessing a publisher component directly

# Given: Hooks.build(publisher: MyKafkaPublisher.new)
class MyHandler < Hooks::Plugins::Handlers::Base
  def call(payload:, headers:, env:, config:)
    publisher.send_message(payload, topic: "webhooks")
    { status: "published" }
  end
end

Using a callable component (Proc/Lambda)

# Given: Hooks.build(notifier: ->(msg) { puts "Notification: #{msg}" })
class MyHandler < Hooks::Plugins::Handlers::Base
  def call(payload:, headers:, env:, config:)
    notifier.call("New webhook received")
    # Or use the shorthand syntax:
    notifier("Processing webhook for #{payload['user_id']}")
    { status: "notified" }
  end
end

Using a service object

# Given: Hooks.build(email_service: EmailService.new(api_key: "..."))
class MyHandler < Hooks::Plugins::Handlers::Base
  def call(payload:, headers:, env:, config:)
    email_service.send_notification(
      to: payload['email'],
      subject: "Webhook Processed",
      body: "Your webhook has been successfully processed"
    )
    { status: "email_sent" }
  end
end

Passing blocks to components

# Given: Hooks.build(batch_processor: BatchProcessor.new)
class MyHandler < Hooks::Plugins::Handlers::Base
  def call(payload:, headers:, env:, config:)
    batch_processor.process_with_callback(payload) do |result|
      log.info("Batch processing completed: #{result}")
    end
    { status: "batch_queued" }
  end
end

Parameters:

  • method_name (Symbol)

    The method name being called

  • args (Array)

    Arguments passed to the method

  • kwargs (Hash)

    Keyword arguments passed to the method

  • block (Proc)

    Block passed to the method

Returns:

  • (Object)

    The user component or result of method call

Raises:

  • (NoMethodError)

    If component doesn’t exist and no super method available



159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
# File 'lib/hooks/core/component_access.rb', line 159

def method_missing(method_name, *args, **kwargs, &block)
  component = Hooks::Core::GlobalComponents.get_extra_component(method_name)

  if component
    # If called with arguments or block, try to call the component as a method
    if args.any? || kwargs.any? || block
      component.call(*args, **kwargs, &block)
    else
      # Otherwise return the component itself
      component
    end
  else
    # Fall back to normal method_missing behavior
    super
  end
end

Instance Method Details

#failbotHooks::Plugins::Instruments::Failbot

Global failbot component accessor Provides access to the global failbot component for reporting errors to services like Sentry, Rollbar, etc.

Examples:

Reporting an error

failbot.report("Something went wrong", { context: "additional info" })

Returns:



94
95
96
# File 'lib/hooks/core/component_access.rb', line 94

def failbot
  Hooks::Core::GlobalComponents.failbot
end

#logHooks::Log

Short logger accessor Provides a convenient way to log messages without needing to reference the full Hooks::Log namespace.

Examples:

Logging an error

log.error("Something went wrong")

Returns:

  • (Hooks::Log)

    Logger instance for logging messages



70
71
72
# File 'lib/hooks/core/component_access.rb', line 70

def log
  Hooks::Log.instance
end

#respond_to_missing?(method_name, include_private = false) ⇒ Boolean

Respond to user-defined component names

This method ensures that handlers properly respond to user-defined component names, enabling proper method introspection and duck typing support.

Examples:

Checking if a component is available

class MyHandler < Hooks::Plugins::Handlers::Base
  def call(payload:, headers:, env:, config:)
    if respond_to?(:publisher)
      publisher.send_message(payload)
      { status: "published" }
    else
      log.warn("Publisher not available, skipping message send")
      { status: "skipped" }
    end
  end
end

Conditional component usage

class MyHandler < Hooks::Plugins::Handlers::Base
  def call(payload:, headers:, env:, config:)
    results = { status: "processed" }

    # Only use analytics if available
    if respond_to?(:analytics)
      analytics.track_event("webhook_processed", payload)
      results[:analytics] = "tracked"
    end

    # Only send notifications if notifier is available
    if respond_to?(:notifier)
      notifier.call("Webhook processed: #{payload['id']}")
      results[:notification] = "sent"
    end

    results
  end
end

Parameters:

  • method_name (Symbol)

    The method name being checked

  • include_private (Boolean) (defaults to: false)

    Whether to include private methods

Returns:

  • (Boolean)

    True if method exists or is a user component



218
219
220
# File 'lib/hooks/core/component_access.rb', line 218

def respond_to_missing?(method_name, include_private = false)
  Hooks::Core::GlobalComponents.extra_component_exists?(method_name) || super
end

#statsHooks::Plugins::Instruments::Stats

Global stats component accessor Provides access to the global stats component for reporting metrics to services like DataDog, New Relic, etc.

Examples:

Recording a metric

stats.increment("webhook.processed", { handler: "MyHandler" })

Returns:



82
83
84
# File 'lib/hooks/core/component_access.rb', line 82

def stats
  Hooks::Core::GlobalComponents.stats
end