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.



104
105
106
107
108
109
110
111
112
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
# File 'lib/where_is_waldo/configuration.rb', line 104

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

  # 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



33
34
35
# File 'lib/where_is_waldo/configuration.rb', line 33

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: ...).



57
58
59
# File 'lib/where_is_waldo/configuration.rb', line 57

def broadcast_audience
  @broadcast_audience
end

#channel_nameObject

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



33
34
35
# File 'lib/where_is_waldo/configuration.rb', line 33

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

#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.



102
103
104
# File 'lib/where_is_waldo/configuration.rb', line 102

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).



77
78
79
# File 'lib/where_is_waldo/configuration.rb', line 77

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).



77
78
79
# File 'lib/where_is_waldo/configuration.rb', line 77

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 }



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

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.



102
103
104
# File 'lib/where_is_waldo/configuration.rb', line 102

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).



77
78
79
# File 'lib/where_is_waldo/configuration.rb', line 77

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.



102
103
104
# File 'lib/where_is_waldo/configuration.rb', line 102

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.



88
89
90
# File 'lib/where_is_waldo/configuration.rb', line 88

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.



88
89
90
# File 'lib/where_is_waldo/configuration.rb', line 88

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?
}


50
51
52
# File 'lib/where_is_waldo/configuration.rb', line 50

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



215
216
217
218
219
220
221
222
223
224
225
# File 'lib/where_is_waldo/configuration.rb', line 215

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).



164
165
166
167
168
169
# File 'lib/where_is_waldo/configuration.rb', line 164

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).



172
173
174
175
176
177
178
179
180
# File 'lib/where_is_waldo/configuration.rb', line 172

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.



208
209
210
211
212
# File 'lib/where_is_waldo/configuration.rb', line 208

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).



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

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).



200
201
202
203
204
# File 'lib/where_is_waldo/configuration.rb', line 200

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.



189
190
191
192
193
194
195
196
197
# File 'lib/where_is_waldo/configuration.rb', line 189

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)


183
184
185
# File 'lib/where_is_waldo/configuration.rb', line 183

def roster_enabled?
  !roster_org.nil?
end

#subject_class_constantObject

Helper to get subject class constant



149
150
151
152
153
# File 'lib/where_is_waldo/configuration.rb', line 149

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



144
145
146
# File 'lib/where_is_waldo/configuration.rb', line 144

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