Class: Livechat::Conversation

Inherits:
ApplicationRecord show all
Defined in:
app/models/livechat/conversation.rb

Overview

One thread per visitor — like a phone line, not a ticket queue. A visitor writing into a resolved conversation reopens it; history stays in place. Visitor identity is loose (no foreign key to the host's user table): signed-in visitors are keyed by visitor_id, guests by a cookie token.

Constant Summary collapse

STATUSES =
%w[open resolved].freeze
TYPING_TTL =
8.seconds

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.claim!(visitor_token:, visitor_id:, visitor_label: nil) ⇒ Object

A guest who signs in keeps their conversation: threads started under the cookie token are adopted by the user, once, permanently.



39
40
41
42
43
44
# File 'app/models/livechat/conversation.rb', line 39

def self.claim!(visitor_token:, visitor_id:, visitor_label: nil)
  return if visitor_token.blank? || visitor_id.blank?

  where(visitor_token: visitor_token, visitor_id: nil)
    .update_all(visitor_id: visitor_id.to_s, visitor_label: visitor_label)
end

.for_visitor(visitor_id: nil, visitor_token: nil) ⇒ Object

The visitor's ongoing thread. Signed-in identity wins; guests are found by their cookie token (only unclaimed threads — a token that has been adopted by a user belongs to that user now).



29
30
31
32
33
34
35
# File 'app/models/livechat/conversation.rb', line 29

def self.for_visitor(visitor_id: nil, visitor_token: nil)
  if visitor_id.present?
    where(visitor_id: visitor_id.to_s).order(id: :desc).first
  elsif visitor_token.present?
    where(visitor_token: visitor_token, visitor_id: nil).order(id: :desc).first
  end
end

Instance Method Details

#clear_typing!(author_type) ⇒ Object



114
115
116
# File 'app/models/livechat/conversation.rb', line 114

def clear_typing!(author_type)
  Rails.cache.delete(typing_cache_key(author_type))
end

#display_nameObject



102
103
104
# File 'app/models/livechat/conversation.rb', line 102

def display_name
  visitor_label.presence || visitor_email.presence || "Visitor ##{id}"
end

#mark_read_for_agent!Object



94
95
96
# File 'app/models/livechat/conversation.rb', line 94

def mark_read_for_agent!
  messages.from_visitor.unread.update_all(read_at: Time.current)
end

#mark_read_for_visitor!Object



98
99
100
# File 'app/models/livechat/conversation.rb', line 98

def mark_read_for_visitor!
  messages.from_agent.unread.update_all(read_at: Time.current)
end

#post_agent_message!(body:, agent_id:, agent_label:, files: nil) ⇒ Object



57
58
59
60
61
62
63
64
# File 'app/models/livechat/conversation.rb', line 57

def post_agent_message!(body:, agent_id:, agent_label:, files: nil)
  message = messages.new(author_type: 'agent', body: body,
                         agent_id: agent_id.to_s, agent_label: agent_label)
  attach(message, files)
  message.save!
  clear_typing!('agent')
  message
end

#post_visitor_message!(body, files: nil) ⇒ Object



46
47
48
49
50
51
52
53
54
55
# File 'app/models/livechat/conversation.rb', line 46

def post_visitor_message!(body, files: nil)
  transaction do
    update!(status: 'open') if resolved?
    message = messages.new(author_type: 'visitor', body: body)
    attach(message, files)
    message.save!
    clear_typing!('visitor')
    message
  end
end

#register_message(message) ⇒ Object

Denormalized inbox-list fields, refreshed on every message. Written with update_columns: cheap, no callbacks, no updated_at churn.



86
87
88
89
90
# File 'app/models/livechat/conversation.rb', line 86

def register_message(message)
  updates = { last_activity_at: message.created_at }
  updates[:last_message_preview] = message.preview unless message.system?
  update_columns(updates)
end

#reopen_by!(agent_label) ⇒ Object



75
76
77
78
79
80
81
82
# File 'app/models/livechat/conversation.rb', line 75

def reopen_by!(agent_label)
  return if open?

  transaction do
    update!(status: 'open')
    messages.create!(author_type: 'system', event: 'reopened', agent_label: agent_label)
  end
end

#resolve_by!(agent_label) ⇒ Object



66
67
68
69
70
71
72
73
# File 'app/models/livechat/conversation.rb', line 66

def resolve_by!(agent_label)
  return if resolved?

  transaction do
    update!(status: 'resolved')
    messages.create!(author_type: 'system', event: 'resolved', agent_label: agent_label)
  end
end

#typing!(author_type) ⇒ Object



106
107
108
# File 'app/models/livechat/conversation.rb', line 106

def typing!(author_type)
  Rails.cache.write(typing_cache_key(author_type), true, expires_in: TYPING_TTL)
end

#typing?(author_type) ⇒ Boolean

Returns:

  • (Boolean)


110
111
112
# File 'app/models/livechat/conversation.rb', line 110

def typing?(author_type)
  Rails.cache.exist?(typing_cache_key(author_type))
end

#unread_from_visitor_countObject



92
# File 'app/models/livechat/conversation.rb', line 92

def unread_from_visitor_count = messages.from_visitor.unread.count