Class: TrackRelay::Subscribers::Base
- Inherits:
-
Object
- Object
- TrackRelay::Subscribers::Base
- Defined in:
- lib/track_relay/subscribers/base.rb
Overview
Base class for all track_relay subscribers.
Each subscriber receives an EventPayload via #handle, which routes to one of two paths:
- **sync** — `safe_deliver(payload)` is invoked inline on the
calling thread. Used when the subclass calls {.synchronous!}
or when {Configuration#force_synchronous} is `true`.
- **async** — {DeliveryJob} is enqueued with the subscriber's
class name and the payload's serialized form. The job calls
`safe_deliver` on a fresh instance when it eventually runs.
**Error contract (locked in 01-CONTEXT.md, 01-05-PLAN.md):** ‘safe_deliver` returns `nil` on success or the StandardError on failure — it NEVER re-raises inline. The Dispatcher collects those returns during fan-out and re-raises the first one afterwards, but only when Configuration#swallow_subscriber_errors is `false`. This guarantees that one bad subscriber never blocks peers, while still letting dev/test surface failures loudly once everyone has had their chance.
Direct Known Subclasses
Class Method Summary collapse
-
.coerce_event_set(value) ⇒ Set<Symbol>?
Coerce a filter input (Array<Symbol|String>, Set, single Symbol, or nil) into a ‘Set<Symbol>` or `nil`.
-
.filter(only: nil, except: nil) ⇒ void
Class-level DSL for declaring an event-name filter.
-
.synchronous! ⇒ Boolean
Mark this subclass as synchronous.
Instance Method Summary collapse
-
#deliver(payload) ⇒ void
Implement in subclasses to receive an EventPayload.
-
#except_events ⇒ Set<Symbol>?
Read the effective ‘except:` filter for this instance — the singleton override (set by TrackRelay.subscribe) when present, otherwise the class-level default declared via Base.filter.
-
#handle(payload) ⇒ nil, StandardError
Route ‘payload` to the sync or async path.
-
#only_events ⇒ Set<Symbol>?
Read the effective ‘only:` filter for this instance — the singleton override (set by TrackRelay.subscribe) when present, otherwise the class-level default declared via Base.filter.
-
#safe_deliver(payload) ⇒ nil, StandardError
Wrap #deliver with the per-subscriber rescue.
-
#set_filter_overrides!(only: nil, except: nil) ⇒ self
Set per-instance ‘only:` / `except:` filter overrides on this subscriber.
Class Method Details
.coerce_event_set(value) ⇒ Set<Symbol>?
Coerce a filter input (Array<Symbol|String>, Set, single Symbol, or nil) into a ‘Set<Symbol>` or `nil`. Internal helper shared by the class-level filter DSL and the per-instance override path (#set_filter_overrides!, used by TrackRelay.subscribe).
81 82 83 84 |
# File 'lib/track_relay/subscribers/base.rb', line 81 def self.coerce_event_set(value) return nil if value.nil? Set.new(Array(value).map(&:to_sym)) end |
.filter(only: nil, except: nil) ⇒ void
This method returns an undefined value.
Class-level DSL for declaring an event-name filter.
class MySubscriber < TrackRelay::Subscribers::Base
filter only: %i[purchase sign_up]
end
‘only:` and `except:` are mutually exclusive in spirit but not enforced as such — if both are set, `only:` wins (an event must be in the allow-list AND not in the deny-list to pass). Pass `nil` to clear a previously set filter.
69 70 71 72 |
# File 'lib/track_relay/subscribers/base.rb', line 69 def self.filter(only: nil, except: nil) self.only_events = coerce_event_set(only) self.except_events = coerce_event_set(except) end |
.synchronous! ⇒ Boolean
Mark this subclass as synchronous. Calls to #handle will run ‘safe_deliver` inline rather than enqueueing a DeliveryJob.
49 50 51 |
# File 'lib/track_relay/subscribers/base.rb', line 49 def self.synchronous! self.synchronous = true end |
Instance Method Details
#deliver(payload) ⇒ void
This method returns an undefined value.
Implement in subclasses to receive an EventPayload.
91 92 93 |
# File 'lib/track_relay/subscribers/base.rb', line 91 def deliver(payload) raise NotImplementedError, "#{self.class.name} must implement #deliver(payload)" end |
#except_events ⇒ Set<Symbol>?
Read the effective ‘except:` filter for this instance — the singleton override (set by TrackRelay.subscribe) when present, otherwise the class-level default declared via filter.
201 202 203 204 205 206 207 |
# File 'lib/track_relay/subscribers/base.rb', line 201 def except_events if singleton_class.instance_variable_defined?(:@except_events_override) singleton_class.instance_variable_get(:@except_events_override) else self.class.except_events end end |
#handle(payload) ⇒ nil, StandardError
Route ‘payload` to the sync or async path.
Returns: ‘nil` on success, the StandardError on a sync failure, or `nil` on the async path (the job runs later — its eventual failure mode is handled inside DeliveryJob#perform).
**Filter gate (Plan 02-01):** if ‘only_events` / `except_events` exclude `payload.name`, return `nil` immediately — BEFORE the sync/async branch and BEFORE `safe_deliver`’s rescue boundary. A filtered event with a buggy ‘#deliver` therefore neither runs nor logs.
109 110 111 112 113 114 115 116 117 118 |
# File 'lib/track_relay/subscribers/base.rb', line 109 def handle(payload) return nil if filtered?(payload.name.to_sym) if self.class.synchronous || TrackRelay.config.force_synchronous safe_deliver(payload) else DeliveryJob.perform_later(self.class.name, payload.to_h) nil end end |
#only_events ⇒ Set<Symbol>?
Read the effective ‘only:` filter for this instance — the singleton override (set by TrackRelay.subscribe) when present, otherwise the class-level default declared via filter.
188 189 190 191 192 193 194 |
# File 'lib/track_relay/subscribers/base.rb', line 188 def only_events if singleton_class.instance_variable_defined?(:@only_events_override) singleton_class.instance_variable_get(:@only_events_override) else self.class.only_events end end |
#safe_deliver(payload) ⇒ nil, StandardError
Wrap #deliver with the per-subscriber rescue.
Returns ‘nil` on success or the StandardError on failure. ALWAYS logs the failure (via `Rails.logger.error`) when running under Rails. NEVER re-raises arbitrary `StandardError`s — the Dispatcher (or DeliveryJob) makes the loudness decision based on Configuration#swallow_subscriber_errors.
**REQ-23 carve-out (Plan 02-04):** DeliveryRetriableError and DeliveryDiscardableError are RE-RAISED unconditionally — even when ‘swallow_subscriber_errors = true` (the production default). ActiveJob’s ‘retry_on` / `discard_on` macros only fire on raised exceptions; without this carve-out the GA4 retry/discard policy in DeliveryJob would be silently broken in production because `safe_deliver` would catch the exception, return it as a value, and the job would think delivery succeeded.
The carve-out is INTENTIONALLY NARROW: arbitrary ‘StandardError`s still flow through the existing log-and-return path — REQ-23’s blanket-rescue contract is preserved for everything outside of the typed retry/discard exception classes.
145 146 147 148 149 150 151 152 153 154 155 156 157 158 |
# File 'lib/track_relay/subscribers/base.rb', line 145 def safe_deliver(payload) deliver(payload) nil rescue TrackRelay::DeliveryRetriableError, TrackRelay::DeliveryDiscardableError # Carve-out: ActiveJob retry_on/discard_on must see these. # Do NOT log here — the DeliveryJob's retry path will log on # eventual exhaustion, and the discard path is an intentional # drop. Logging on every retry attempt would spam the log with # transient blips that resolve on retry. raise rescue => e log_failure(e) e end |
#set_filter_overrides!(only: nil, except: nil) ⇒ self
Set per-instance ‘only:` / `except:` filter overrides on this subscriber. Used by TrackRelay.subscribe so a single subscriber class can be registered multiple times with different filters.
Each non-nil argument is coerced via coerce_event_set and stored on the instance’s singleton class so it does not bleed across instances or mutate the class-level defaults declared via filter. Passing ‘nil` for either argument leaves that override untouched (the instance falls through to the class default).
173 174 175 176 177 178 179 180 181 |
# File 'lib/track_relay/subscribers/base.rb', line 173 def set_filter_overrides!(only: nil, except: nil) unless only.nil? singleton_class.instance_variable_set(:@only_events_override, self.class.coerce_event_set(only)) end unless except.nil? singleton_class.instance_variable_set(:@except_events_override, self.class.coerce_event_set(except)) end self end |