Class: TrackRelay::Subscribers::Ahoy
- Defined in:
- lib/track_relay/subscribers/ahoy.rb
Overview
Server-side Ahoy subscriber (REQ-09).
Routes catalog events through Ahoy's only public tracking surface
— controller.ahoy.track(name, properties) — by reading
Current.controller on the synchronous request
thread. When no controller is in scope (background job, rake
task, console), or the host application does not include
Ahoy::Controller in its ApplicationController, the subscriber
logs a warning and skips delivery — it does NOT raise, does NOT
enqueue a DeliveryJob, and does NOT touch any internal Ahoy
API.
Why synchronous (not async like GA4)
Ahoy::Tracker is bound to the live request — it wraps the
controller's cookie jar and visit lifecycle. By the time a
DeliveryJob runs, Rails.application.executor.wrap has
already cleared ActiveSupport::CurrentAttributes, so
Current.controller is nil and the live tracker instance is
unreachable. Base.synchronous! therefore opts the subscriber into
inline delivery on the request thread — #handle calls
safe_deliver(payload) directly instead of enqueueing a job.
tracker.track is an in-process database write (it calls
@store.track_event(data) which does event_model.create! or
equivalent), not a network call, so synchronous delivery adds
negligible request overhead and matches how Ahoy itself works
(Ahoy::Trackable wires track as an inline before_action helper).
Why no require "ahoy"
The subscriber must load cleanly in non-Ahoy host applications
(the gem ships with the file in lib/track_relay.rb's require
manifest unconditionally). Duck-typing via
controller.respond_to?(:ahoy, true) handles the absent-Ahoy
case without a top-level require — the same pattern used by
ClientId::AhoyVisitor.
Why no Ahoy::Event.create! / Ahoy::Tracker.new
Ahoy::Tracker is the sole public tracking surface. Internal
APIs (Ahoy::Event.create!, Ahoy::Visit#track — which does NOT
exist on the visit model) are off-limits because they bypass
Ahoy's bot-exclusion store, user-method config, and visit
association logic. The subscriber dispatches via
controller.ahoy.track(name, properties) only.
Skip conditions
All three skip paths log a Rails.logger.warn line of the form
[track_relay] Ahoy subscriber skipping delivery — <reason> and
return from #deliver. They MUST NOT raise — host applications
that boot without Ahoy or call TrackRelay.track from a job
must not crash.
Current.controlleris nil — job, rake, or console context.- The controller does not
respond_to?(:ahoy, true)—Ahoy::Controllerwas neverincluded. controller.ahoyreturns nil — defensive coverage for a controller that has the helper but no live tracker yet.
Instance Method Summary collapse
-
#deliver(payload) ⇒ void
Dispatch
payloadtocontroller.ahoy.trackwhen a live controller with an Ahoy tracker is in scope.
Methods inherited from Base
coerce_event_set, #except_events, filter, #handle, #only_events, #prepare, #safe_deliver, #set_filter_overrides!, synchronous!
Instance Method Details
#deliver(payload) ⇒ void
This method returns an undefined value.
Dispatch payload to controller.ahoy.track when a live
controller with an Ahoy tracker is in scope. Skip-and-warn
otherwise.
77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 |
# File 'lib/track_relay/subscribers/ahoy.rb', line 77 def deliver(payload) controller = TrackRelay::Current.controller unless controller&.respond_to?(:ahoy, true) log_skip("no controller or ahoy tracker in context") return end tracker = controller.ahoy unless tracker log_skip("controller.ahoy returned nil") return end tracker.track(payload.name.to_s, payload.params) end |