Class: Protege::MessageSearch

Inherits:
Object
  • Object
show all
Defined in:
app/services/protege/message_search.rb

Overview

Cross-persona email search behind the console's search page — a PORO service object (not an ActiveRecord model, and not the persona-scoped search_emails agent tool).

Each field narrows the Message relation: from scans the sender, recipients spans the to/cc/bcc lists, subject and body (text + html) scan their columns, and attachments matches by stored filename. Filled fields are ANDed onto the same message row, so a message only matches when it satisfies every supplied field at once. #results returns the matching messages themselves (newest sent first) — the console lists the emails and links each to its thread — rather than collapsing to threads, so the result grain matches the email-grained query.

Matching mirrors search_emails: case-insensitive LIKE with sanitize_sql_like so the term's own +%+/+_+ are literals — SQLite-safe, which is the dashboard's database.

Constant Summary collapse

FIELD_COLUMNS =

Maps each column-backed search field to the message columns it scans. Columns are a fixed allowlist, so they are safe to interpolate into the LIKE clause (the term is a bound param).

{
  from:       %w[from_address],
  recipients: %w[to_addresses cc_addresses bcc_addresses],
  subject:    %w[subject],
  body:       %w[text_body html_body]
}.freeze

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(from: nil, recipients: nil, subject: nil, body: nil, attachments: nil) ⇒ MessageSearch

Returns a new instance of MessageSearch.

Parameters:

  • from (String, nil) (defaults to: nil)

    sender term

  • recipients (String, nil) (defaults to: nil)

    term matched against to/cc/bcc

  • subject (String, nil) (defaults to: nil)

    subject term

  • body (String, nil) (defaults to: nil)

    term matched against the text and html bodies

  • attachments (String, nil) (defaults to: nil)

    attachment-filename term



45
46
47
48
# File 'app/services/protege/message_search.rb', line 45

def initialize(from: nil, recipients: nil, subject: nil, body: nil, attachments: nil)
  @column_terms = { from:, recipients:, subject:, body: }
  @attachments  = attachments
end

Class Method Details

.from_params(params) ⇒ Protege::MessageSearch

Build a search from request params, reading the field keys it understands.

Parameters:

  • params (ActionController::Parameters, Hash)

    the request params

Returns:



31
32
33
34
35
36
37
# File 'app/services/protege/message_search.rb', line 31

def from_params(params)
  new(from:        params[:from],
      recipients:  params[:recipients],
      subject:     params[:subject],
      body:        params[:body],
      attachments: params[:attachments])
end

Instance Method Details

#active?Boolean

Whether any field carries a term to search on.

Returns:

  • (Boolean)


53
54
55
# File 'app/services/protege/message_search.rb', line 53

def active?
  (@column_terms.values + [@attachments]).any?(&:present?)
end

#resultsActiveRecord::Relation<Protege::Message>

The matching messages, newest sent first, each with its thread preloaded for the result row; empty when no field is filled.

preload (a separate query) rather than includes avoids a JOIN — the search columns subject and from_address also exist on protege_email_threads, so a join would make them ambiguous.

Returns:



64
65
66
67
68
# File 'app/services/protege/message_search.rb', line 64

def results
  return Message.none unless active?

  message_scope.preload(:email_thread).order(sent_at: :desc)
end