Class: WhereIsWaldo::Configuration

Inherits:
Object
  • Object
show all
Defined in:
lib/where_is_waldo/configuration.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeConfiguration

Returns a new instance of Configuration.



113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
# File 'lib/where_is_waldo/configuration.rb', line 113

def initialize
  # Storage defaults
  @adapter = :database
  @table_name = "presences"
  @redis_client = nil
  @redis_prefix = "where_is_waldo"

  # Column defaults
  @session_column = :session_id
  @subject_column = :subject_id

  # Subject model (required)
  @subject_class = nil
  @subject_data_proc = nil

  # Timing defaults
  @timeout = 90
  @heartbeat_interval = 30

  # Observability defaults
  @ignore_heartbeat_apm = true

  # ActionCable defaults
  @channel_name = "WhereIsWaldo::PresenceChannel"
  @authenticate_proc = nil
  @suppress_presence_proc = nil

  # Broadcastable default audience (set per app)
  @broadcast_audience = nil

  # Presence roster (set per app to enable)
  @roster_org = nil
  @roster_members = nil
  @roster_members_association = nil
  @roster_visible_to = nil
  @roster_viewers_of = nil
  @roster_mode = :poll
  @roster_poll_interval = 15
  @roster_cache_ttl = 90
  @roster_nudge_jitter = 0.5
end

Instance Attribute Details

#adapterObject

Storage settings :adapter - :database or :redis :table_name - defaults to 'presences' :redis_client - custom Redis instance (optional) :redis_prefix - key prefix for Redis (for multi-app setups)



10
11
12
# File 'lib/where_is_waldo/configuration.rb', line 10

def adapter
  @adapter
end

#authenticate_procObject

ActionCable settings :channel_name - defaults to 'WhereIsWaldo::PresenceChannel' :authenticate_proc - proc to authenticate connection, receives request



42
43
44
# File 'lib/where_is_waldo/configuration.rb', line 42

def authenticate_proc
  @authenticate_proc
end

#broadcast_audienceObject

Default audience resolver for the Broadcastable concern. A lambda that, given a record, returns the AR scope to broadcast to (e.g. that record's account members). Set once per app to match its container, e.g.:

config.broadcast_audience = ->(rec) { rec..users }

Models may override per-model via broadcasts_realtime(scope: ...).



66
67
68
# File 'lib/where_is_waldo/configuration.rb', line 66

def broadcast_audience
  @broadcast_audience
end

#channel_nameObject

ActionCable settings :channel_name - defaults to 'WhereIsWaldo::PresenceChannel' :authenticate_proc - proc to authenticate connection, receives request



42
43
44
# File 'lib/where_is_waldo/configuration.rb', line 42

def channel_name
  @channel_name
end

#heartbeat_intervalObject

Timing :timeout - seconds until considered offline (default: 90) :heartbeat_interval - expected heartbeat frequency (default: 30)



28
29
30
# File 'lib/where_is_waldo/configuration.rb', line 28

def heartbeat_interval
  @heartbeat_interval
end

#ignore_heartbeat_apmObject

Observability :ignore_heartbeat_apm - when true (default) and a supported APM agent (New Relic) is loaded in the host app, presence heartbeats are kept out of APM as their own transactions. Heartbeats are the highest-volume, least-interesting cable action, so reporting each one distorts throughput and average response time without adding signal. Set false to report them like any other action. See WhereIsWaldo::Apm.



37
38
39
# File 'lib/where_is_waldo/configuration.rb', line 37

def ignore_heartbeat_apm
  @ignore_heartbeat_apm
end

#redis_clientObject

Storage settings :adapter - :database or :redis :table_name - defaults to 'presences' :redis_client - custom Redis instance (optional) :redis_prefix - key prefix for Redis (for multi-app setups)



10
11
12
# File 'lib/where_is_waldo/configuration.rb', line 10

def redis_client
  @redis_client
end

#redis_prefixObject

Storage settings :adapter - :database or :redis :table_name - defaults to 'presences' :redis_client - custom Redis instance (optional) :redis_prefix - key prefix for Redis (for multi-app setups)



10
11
12
# File 'lib/where_is_waldo/configuration.rb', line 10

def redis_prefix
  @redis_prefix
end

#roster_cache_ttlObject

Tuning (pull/nudge). roster_nudge_jitter (seconds) spreads clients' re-poll after a nudge so a change doesn't stampede every viewer at once.



111
112
113
# File 'lib/where_is_waldo/configuration.rb', line 111

def roster_cache_ttl
  @roster_cache_ttl
end

#roster_membersObject

Live presence roster ("who's around in my org/account") ===

roster_org: given a subject, return the org/container record that defines the roster boundary. Its class + id key the single shared roster ActionCable stream, so every member of the same org subscribes to ONE stream and a presence change is a single O(1) broadcast (not per-member fan-out). Required to enable the roster feature — nil leaves it inert. config.roster_org = ->(subject) { subject.account }

roster_members: given that org, return the AR scope of member subjects shown in the roster. Optional — defaults to org.public_send() inferred from subject_class (User => :users). Provide it to scope the list, e.g. only active members: config.roster_members = ->(org) { org.users.active }

roster_members_association: association used to build the default roster from the org when roster_members is unset (defaults to the pluralized subject_class, e.g. :users).



86
87
88
# File 'lib/where_is_waldo/configuration.rb', line 86

def roster_members
  @roster_members
end

#roster_members_associationObject

Live presence roster ("who's around in my org/account") ===

roster_org: given a subject, return the org/container record that defines the roster boundary. Its class + id key the single shared roster ActionCable stream, so every member of the same org subscribes to ONE stream and a presence change is a single O(1) broadcast (not per-member fan-out). Required to enable the roster feature — nil leaves it inert. config.roster_org = ->(subject) { subject.account }

roster_members: given that org, return the AR scope of member subjects shown in the roster. Optional — defaults to org.public_send() inferred from subject_class (User => :users). Provide it to scope the list, e.g. only active members: config.roster_members = ->(org) { org.users.active }

roster_members_association: association used to build the default roster from the org when roster_members is unset (defaults to the pluralized subject_class, e.g. :users).



86
87
88
# File 'lib/where_is_waldo/configuration.rb', line 86

def roster_members_association
  @roster_members_association
end

#roster_modeObject

roster_mode: delivery strategy, per account. A symbol, or a callable resolving an account -> mode. MUST be a function of the account (uniform for all its members). Default :poll (safe: server-side filtered). :poll - heartbeat/poll, server-filtered, ~interval latency :broadcast - instant shared-stream push, NO filtering (open account) :nudge - :poll + content-free trigger (Phase 2) :fanout - per-viewer push (Phase 3) config.roster_mode = ->(account) { account.everyone_admin? ? :broadcast : :poll }



107
108
109
# File 'lib/where_is_waldo/configuration.rb', line 107

def roster_mode
  @roster_mode
end

#roster_nudge_jitterObject

Tuning (pull/nudge). roster_nudge_jitter (seconds) spreads clients' re-poll after a nudge so a change doesn't stampede every viewer at once.



111
112
113
# File 'lib/where_is_waldo/configuration.rb', line 111

def roster_nudge_jitter
  @roster_nudge_jitter
end

#roster_orgObject

Live presence roster ("who's around in my org/account") ===

roster_org: given a subject, return the org/container record that defines the roster boundary. Its class + id key the single shared roster ActionCable stream, so every member of the same org subscribes to ONE stream and a presence change is a single O(1) broadcast (not per-member fan-out). Required to enable the roster feature — nil leaves it inert. config.roster_org = ->(subject) { subject.account }

roster_members: given that org, return the AR scope of member subjects shown in the roster. Optional — defaults to org.public_send() inferred from subject_class (User => :users). Provide it to scope the list, e.g. only active members: config.roster_members = ->(org) { org.users.active }

roster_members_association: association used to build the default roster from the org when roster_members is unset (defaults to the pluralized subject_class, e.g. :users).



86
87
88
# File 'lib/where_is_waldo/configuration.rb', line 86

def roster_org
  @roster_org
end

#roster_poll_intervalObject

Tuning (pull/nudge). roster_nudge_jitter (seconds) spreads clients' re-poll after a nudge so a change doesn't stampede every viewer at once.



111
112
113
# File 'lib/where_is_waldo/configuration.rb', line 111

def roster_poll_interval
  @roster_poll_interval
end

#roster_viewers_ofObject

roster_visible_to: given a VIEWER, return the AR scope of subjects that viewer may see (for :poll/:nudge snapshots + diffs). Handles any visibility rule server-side. Defaults to the viewer's whole org roster (everyone-sees-everyone) when unset. config.roster_visible_to = ->(viewer) { viewer.visible_users }

roster_viewers_of: given a SUBJECT, return the AR scope of viewers allowed to see it (ONLY used by :fanout, Phase 3). Must be the exact inverse of roster_visible_to.



97
98
99
# File 'lib/where_is_waldo/configuration.rb', line 97

def roster_viewers_of
  @roster_viewers_of
end

#roster_visible_toObject

roster_visible_to: given a VIEWER, return the AR scope of subjects that viewer may see (for :poll/:nudge snapshots + diffs). Handles any visibility rule server-side. Defaults to the viewer's whole org roster (everyone-sees-everyone) when unset. config.roster_visible_to = ->(viewer) { viewer.visible_users }

roster_viewers_of: given a SUBJECT, return the AR scope of viewers allowed to see it (ONLY used by :fanout, Phase 3). Must be the exact inverse of roster_visible_to.



97
98
99
# File 'lib/where_is_waldo/configuration.rb', line 97

def roster_visible_to
  @roster_visible_to
end

#session_columnObject

Column names - fully configurable :session_column - unique identifier per connection/tab (e.g., :session_id, :jti) :subject_column - who is present (e.g., :user_id, :member_id, :student_id)



15
16
17
# File 'lib/where_is_waldo/configuration.rb', line 15

def session_column
  @session_column
end

#subject_classObject

Subject model (e.g., 'User', 'Member', 'Student') Required for scope-based broadcasting and queries



19
20
21
# File 'lib/where_is_waldo/configuration.rb', line 19

def subject_class
  @subject_class
end

#subject_columnObject

Column names - fully configurable :session_column - unique identifier per connection/tab (e.g., :session_id, :jti) :subject_column - who is present (e.g., :user_id, :member_id, :student_id)



15
16
17
# File 'lib/where_is_waldo/configuration.rb', line 15

def subject_column
  @subject_column
end

#subject_data_procObject

Optional: proc that returns hash of subject info for presence data Called with the subject record to build presence hash



23
24
25
# File 'lib/where_is_waldo/configuration.rb', line 23

def subject_data_proc
  @subject_data_proc
end

#suppress_presence_procObject

:suppress_presence_proc - callable that decides, per connection, whether to SKIP presence registration for that subscriber. Receives the ActionCable connection (host apps can read session/cookies/env off it, e.g. via connection.request). Returns truthy to suppress.

A suppressed subscriber still subscribes normally — they receive broadcasts to where_is_waldo:subject:<id> (WhereIsWaldo.broadcast_to* signaling) and any other Waldo capability — they just don't register a Presence row / heartbeat / roster transition. Use for cases where the WebSocket session is legitimate but shouldn't be counted as "the subject is present", e.g. support-user impersonation tabs.

config.suppress_presence_proc = ->(connection) {
connection.request.session[:su_user].present?
}


59
60
61
# File 'lib/where_is_waldo/configuration.rb', line 59

def suppress_presence_proc
  @suppress_presence_proc
end

#table_nameObject

Storage settings :adapter - :database or :redis :table_name - defaults to 'presences' :redis_client - custom Redis instance (optional) :redis_prefix - key prefix for Redis (for multi-app setups)



10
11
12
# File 'lib/where_is_waldo/configuration.rb', line 10

def table_name
  @table_name
end

#timeoutObject

Timing :timeout - seconds until considered offline (default: 90) :heartbeat_interval - expected heartbeat frequency (default: 30)



28
29
30
# File 'lib/where_is_waldo/configuration.rb', line 28

def timeout
  @timeout
end

Instance Method Details

#build_subject_data(subject) ⇒ Object

Build subject data hash from a subject record



227
228
229
230
231
232
233
234
235
236
237
# File 'lib/where_is_waldo/configuration.rb', line 227

def build_subject_data(subject)
  return {} unless subject

  if subject_data_proc
    subject_data_proc.call(subject)
  elsif subject.respond_to?(:id)
    { id: subject.id }
  else
    {}
  end
end

#members_associationObject

Association name used to derive the default roster from an org when roster_members is not configured (e.g. subject_class "User" => :users).



176
177
178
179
180
181
# File 'lib/where_is_waldo/configuration.rb', line 176

def members_association
  return roster_members_association if roster_members_association
  return nil if subject_class.blank?

  subject_class.to_s.demodulize.underscore.pluralize.to_sym
end

#resolve_members(org) ⇒ Object

Resolve the AR scope of member subjects for an org (nil if not resolvable).



184
185
186
187
188
189
190
191
192
# File 'lib/where_is_waldo/configuration.rb', line 184

def resolve_members(org)
  return nil unless org

  if roster_members
    roster_members.call(org)
  elsif (assoc = members_association) && org.respond_to?(assoc)
    org.public_send(assoc)
  end
end

#resolve_mode(account) ⇒ Object

Resolve the delivery mode for an account. Callable roster_mode is invoked with the account; a bare symbol is returned as-is. Defaults to :poll.



220
221
222
223
224
# File 'lib/where_is_waldo/configuration.rb', line 220

def resolve_mode()
  mode = roster_mode
  mode = mode.call() if mode.respond_to?(:call)
  (mode || :poll).to_sym
end

#resolve_roster_org(subject) ⇒ Object

Resolve the org/container record for a subject (nil if unset/absent).



168
169
170
171
172
# File 'lib/where_is_waldo/configuration.rb', line 168

def resolve_roster_org(subject)
  return nil unless roster_org && subject

  roster_org.call(subject)
end

#resolve_viewers_of(subject) ⇒ Object

Resolve the AR scope of viewers allowed to see a SUBJECT (:fanout only).



212
213
214
215
216
# File 'lib/where_is_waldo/configuration.rb', line 212

def resolve_viewers_of(subject)
  return nil unless roster_viewers_of && subject

  roster_viewers_of.call(subject)
end

#resolve_visible_to(viewer) ⇒ Object

Resolve the AR scope of subjects a VIEWER may see. Defaults to the viewer's whole org roster (everyone-sees-everyone) when unset.



201
202
203
204
205
206
207
208
209
# File 'lib/where_is_waldo/configuration.rb', line 201

def resolve_visible_to(viewer)
  return nil unless viewer

  if roster_visible_to
    roster_visible_to.call(viewer)
  else
    resolve_members(resolve_roster_org(viewer))
  end
end

#roster_enabled?Boolean

True when the live-presence roster feature is configured.

Returns:

  • (Boolean)


195
196
197
# File 'lib/where_is_waldo/configuration.rb', line 195

def roster_enabled?
  !roster_org.nil?
end

#subject_class_constantObject

Helper to get subject class constant



161
162
163
164
165
# File 'lib/where_is_waldo/configuration.rb', line 161

def subject_class_constant
  return nil if subject_class.blank?

  subject_class.is_a?(String) ? subject_class.safe_constantize : subject_class
end

#timeout_durationObject

Helper to get timeout as duration



156
157
158
# File 'lib/where_is_waldo/configuration.rb', line 156

def timeout_duration
  timeout.is_a?(ActiveSupport::Duration) ? timeout : timeout.seconds
end