Class: WhereIsWaldo::Adapters::DatabaseAdapter

Inherits:
BaseAdapter
  • Object
show all
Defined in:
app/services/where_is_waldo/adapters/database_adapter.rb

Instance Method Summary collapse

Instance Method Details

#cleanup(timeout: nil) ⇒ Object



109
110
111
112
# File 'app/services/where_is_waldo/adapters/database_adapter.rb', line 109

def cleanup(timeout: nil)
  threshold = (timeout || default_timeout).seconds.ago
  Presence.where(last_heartbeat: ...threshold).delete_all
end

#connect(session_id:, subject_id:, metadata: {}) ⇒ Object



6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# File 'app/services/where_is_waldo/adapters/database_adapter.rb', line 6

def connect(session_id:, subject_id:, metadata: {})
  now = Time.current

  attrs = {
    session_column => session_id,
    subject_column => subject_id,
    connected_at: now,
    last_heartbeat: now,
    tab_visible: true,
    subject_active: true,
    last_activity: now,
    metadata: ,
    created_at: now,
    updated_at: now
  }

  # (subject, session) is the unique key — see the install migration
  # template. Keying on session alone would let a caller-supplied
  # session_id colliding across two subjects upsert onto each other's
  # row.
  # rubocop:disable Rails/SkipsModelValidations -- intentional for performance
  Presence.upsert(attrs, unique_by: [subject_column, session_column])
  # rubocop:enable Rails/SkipsModelValidations
  true
rescue StandardError => e
  Rails.logger.error "[WhereIsWaldo] Connect failed: #{e.message}"
  false
end

#disconnect(session_id: nil, subject_id: nil) ⇒ Object



35
36
37
38
39
40
41
42
43
44
45
46
# File 'app/services/where_is_waldo/adapters/database_adapter.rb', line 35

def disconnect(session_id: nil, subject_id: nil)
  raise ArgumentError, "disconnect(session_id:) requires subject_id:" if session_id && !subject_id

  scope = build_lookup_scope(session_id: session_id, subject_id: subject_id)
  scope.delete_all
  true
rescue ArgumentError
  raise
rescue StandardError => e
  Rails.logger.error "[WhereIsWaldo] Disconnect failed: #{e.message}"
  false
end

#heartbeat(session_id:, subject_id:, tab_visible: true, subject_active: true, last_activity_at: nil, metadata: {}) ⇒ Object

rubocop:disable Metrics/ParameterLists, Layout/LineLength



49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# File 'app/services/where_is_waldo/adapters/database_adapter.rb', line 49

def heartbeat(session_id:, subject_id:, tab_visible: true, subject_active: true, last_activity_at: nil, metadata: {})
  # rubocop:enable Metrics/ParameterLists, Layout/LineLength
  now = Time.current
  updates = {
    last_heartbeat: now,
    tab_visible: tab_visible,
    subject_active: subject_active,
    updated_at: now
  }
  # Update last_activity: use JS timestamp if provided, otherwise use current time when active
  if last_activity_at
    updates[:last_activity] = Time.zone.at(last_activity_at / 1000.0)
  elsif subject_active
    updates[:last_activity] = now
  end
  updates[:metadata] =  if .present?

  # Scope by both — a caller-supplied session_id shouldn't be able to
  # heartbeat another subject's row.
  scope = Presence.where(session_column => session_id, subject_column => subject_id)

  # rubocop:disable Rails/SkipsModelValidations -- intentional for performance
  scope.update_all(updates).positive?
  # rubocop:enable Rails/SkipsModelValidations
rescue StandardError => e
  Rails.logger.error "[WhereIsWaldo] Heartbeat failed: #{e.message}"
  false
end

#online_subject_ids(timeout: nil) ⇒ Object



78
79
80
81
82
83
84
# File 'app/services/where_is_waldo/adapters/database_adapter.rb', line 78

def online_subject_ids(timeout: nil)
  threshold = (timeout || default_timeout).seconds.ago

  Presence.where("last_heartbeat > ?", threshold)
          .distinct
          .pluck(subject_column)
end

#session_status(session_id, subject_id) ⇒ Object



103
104
105
106
107
# File 'app/services/where_is_waldo/adapters/database_adapter.rb', line 103

def session_status(session_id, subject_id)
  scope = Presence.where(session_column => session_id, subject_column => subject_id)
  scope = scope.includes(:subject) if config.subject_class_constant
  scope.first&.as_presence_hash
end

#sessions_for_subject(subject_id) ⇒ Object



86
87
88
89
90
# File 'app/services/where_is_waldo/adapters/database_adapter.rb', line 86

def sessions_for_subject(subject_id)
  scope = Presence.where(subject_column => subject_id)
  scope = scope.includes(:subject) if config.subject_class_constant
  scope.map(&:as_presence_hash)
end

#sessions_for_subjects(subject_ids, timeout: nil) ⇒ Object



92
93
94
95
96
97
98
99
100
101
# File 'app/services/where_is_waldo/adapters/database_adapter.rb', line 92

def sessions_for_subjects(subject_ids, timeout: nil)
  ids = Array(subject_ids).compact.uniq
  return {} if ids.empty?

  threshold = (timeout || default_timeout).seconds.ago
  scope = Presence.where(subject_column => ids).where("last_heartbeat > ?", threshold)
  scope = scope.includes(:subject) if config.subject_class_constant
  scope.group_by { |row| row[subject_column] }
       .transform_values { |rows| rows.map(&:as_presence_hash) }
end