Module: Organizations::Callbacks

Defined in:
lib/organizations/callbacks.rb

Overview

Centralized callback dispatch module. Handles executing callbacks with error isolation - callbacks should never break the main organization operations.

Examples:

Dispatching a callback

Callbacks.dispatch(:organization_created, organization: org, user: user)

Constant Summary collapse

EVENTS =

Supported callback events

%i[
  organization_created
  member_invited
  member_joining
  member_joined
  member_removed
  role_changed
  ownership_transferred
  join_request_created
  join_request_approved
  join_request_rejected
  verification_delivery_failed
].freeze
CALLBACK_READERS =

Maps each event to its Configuration reader. Adding an event = one entry here + the EVENTS list + a Configuration attr/block method.

{
  organization_created: :on_organization_created_callback,
  member_invited: :on_member_invited_callback,
  member_joining: :on_member_joining_callback,
  member_joined: :on_member_joined_callback,
  member_removed: :on_member_removed_callback,
  role_changed: :on_role_changed_callback,
  ownership_transferred: :on_ownership_transferred_callback,
  join_request_created: :on_join_request_created_callback,
  join_request_approved: :on_join_request_approved_callback,
  join_request_rejected: :on_join_request_rejected_callback,
  verification_delivery_failed: :on_verification_delivery_failed_callback
}.freeze

Class Method Summary collapse

Class Method Details

.callback_for(event) ⇒ Proc?

Get the callback proc for an event

Parameters:

  • event (Symbol)

    The event type

Returns:

  • (Proc, nil)


62
63
64
65
66
67
68
# File 'lib/organizations/callbacks.rb', line 62

def callback_for(event)
  config = Organizations.configuration
  return nil unless config

  reader = CALLBACK_READERS[event]
  reader ? config.public_send(reader) : nil
end

.dispatch(event, strict: false, **context_data) ⇒ Object

Dispatch a callback event. By default callbacks are isolated (errors logged, not raised). Pass strict: true when callback failures must abort the operation.

Parameters:

  • event (Symbol)

    The event type (e.g., :organization_created)

  • strict (Boolean) (defaults to: false)

    When true, callback errors are re-raised

  • context_data (Hash)

    Data to pass to the callback via CallbackContext



35
36
37
38
39
40
41
# File 'lib/organizations/callbacks.rb', line 35

def dispatch(event, strict: false, **context_data)
  callback = callback_for(event)
  return unless callback

  context = CallbackContext.new(event: event, **context_data)
  strict ? execute_strictly(callback, context) : execute_safely(event, callback, context)
end

.execute_safely(event, callback, context) ⇒ Object

Execute callback with error isolation

Parameters:

  • event (Symbol)

    Event name (for logging)

  • callback (Proc)

    The callback to execute

  • context (CallbackContext)

    The context to pass



74
75
76
77
78
79
80
81
82
# File 'lib/organizations/callbacks.rb', line 74

def execute_safely(event, callback, context)
  return unless callback.respond_to?(:call)

  invoke_callback(callback, context)
rescue StandardError => e
  # Log but don't re-raise - callbacks should never break organization operations
  log_error("[Organizations] Callback error for #{event}: #{e.class}: #{e.message}")
  log_debug(e.backtrace&.join("\n"))
end

.execute_strictly(callback, context) ⇒ Object

Execute callback and propagate any raised errors. Use this in flows where callbacks are expected to veto the operation.



86
87
88
89
90
# File 'lib/organizations/callbacks.rb', line 86

def execute_strictly(callback, context)
  return unless callback.respond_to?(:call)

  invoke_callback(callback, context)
end

.invoke_callback(callback, context) ⇒ Object

Call callback while supporting flexible callback arities. (Review cleanup: the old when 1, -1, -2 / else branches were identical — zero-arity is the only special case.)



95
96
97
98
99
100
101
# File 'lib/organizations/callbacks.rb', line 95

def invoke_callback(callback, context)
  if callback.arity.zero?
    callback.call
  else
    callback.call(context)
  end
end

.log_debug(message) ⇒ Object



120
121
122
123
124
125
126
# File 'lib/organizations/callbacks.rb', line 120

def log_debug(message)
  return unless message

  if defined?(Rails) && Rails.respond_to?(:logger) && Rails.logger&.debug?
    Rails.logger.debug(message)
  end
end

.log_error(message) ⇒ Object

Safe logging that works with or without Rails



104
105
106
107
108
109
110
# File 'lib/organizations/callbacks.rb', line 104

def log_error(message)
  if defined?(Rails) && Rails.respond_to?(:logger) && Rails.logger
    Rails.logger.error(message)
  else
    warn(message)
  end
end

.log_warn(message) ⇒ Object



112
113
114
115
116
117
118
# File 'lib/organizations/callbacks.rb', line 112

def log_warn(message)
  if defined?(Rails) && Rails.respond_to?(:logger) && Rails.logger
    Rails.logger.warn(message)
  else
    warn(message)
  end
end