Class: FullSearch::Highlighter

Inherits:
Object
  • Object
show all
Defined in:
lib/full_search/highlighter.rb

Class Method Summary collapse

Class Method Details

.apply!(records, model, query) ⇒ Object



5
6
7
8
9
10
11
12
# File 'lib/full_search/highlighter.rb', line 5

def self.apply!(records, model, query)
  snippets = build_snippets(model, query)
  if snippets.values.all?(&:nil?) && records.any?
    snippets = manual_snippets(records, model, query)
  end
  records.each { |record| record.full_search_snippet = snippets[record.id] }
  records
end

.apply_fields!(records, model, query) ⇒ Object



14
15
16
17
18
19
20
21
22
23
24
25
26
# File 'lib/full_search/highlighter.rb', line 14

def self.apply_fields!(records, model, query)
  fields = build_field_snippets(model, query)
  if fields.empty? && records.any?
    fields = manual_field_snippets(records, model, query)
  end
  exact_fields = exact_match_field_snippets(records, model, query)
  records.each do |record|
    merged = fields[record.id] || {}
    exact_fields.fetch(record.id, {}).each { |k, v| merged[k] ||= v }
    record.full_search_highlight_fields = merged
  end
  records
end

.build_field_snippets(model, query) ⇒ Object



40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# File 'lib/full_search/highlighter.rb', line 40

def self.build_field_snippets(model, query)
  rows = highlight_rows(model, query)
  dsl = model.full_search_dsl
  fields = dsl.fields
  open_tag = (dsl.highlight_config || { open_tag: "<mark>" })[:open_tag]

  rows.to_h do |row|
    snippets = fields.each_with_object({}) do |field, hash|
      snippet = row["#{field.name}_snippet"].to_s.strip
      key = field.as || field.name
      hash[key] = snippet if snippet.include?(open_tag)
    end
    [row["rowid"], snippets]
  end
end

.build_snippets(model, query) ⇒ Object



30
31
32
33
34
35
36
37
38
# File 'lib/full_search/highlighter.rb', line 30

def self.build_snippets(model, query)
  rows = highlight_rows(model, query)
  cols = model.full_search_dsl.fields.map(&:name)

  rows.to_h do |row|
    text = cols.map { |col| row["#{col}_snippet"] }.compact.join(" ").strip
    [row["rowid"], text.presence]
  end
end

.connectionObject



136
137
138
# File 'lib/full_search/highlighter.rb', line 136

def self.connection
  ActiveRecord::Base.connection
end

.exact_match_field_snippets(records, model, query) ⇒ Object



84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/full_search/highlighter.rb', line 84

def self.exact_match_field_snippets(records, model, query)
  dsl = model.full_search_dsl
  return {} if dsl.exact_matches.empty?

  config = dsl.highlight_config || { open_tag: "<mark>", close_tag: "</mark>" }

  records.to_h do |record|
    snippets = dsl.exact_matches.each_with_object({}) do |em, hash|
      value = exact_match_field_value(em, record)
      highlighted = manual_highlight(value.to_s, query, config)
      hash[em.name.to_s] = highlighted if highlighted.include?(config[:open_tag])
    end
    [record.id, snippets]
  end
end

.exact_match_field_value(em, record) ⇒ Object



100
101
102
103
104
105
106
# File 'lib/full_search/highlighter.rb', line 100

def self.exact_match_field_value(em, record)
  if em.source
    record.instance_exec(&em.source)
  else
    record.public_send(em.name)
  end
end

.highlight_rows(model, query) ⇒ Object



113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
# File 'lib/full_search/highlighter.rb', line 113

def self.highlight_rows(model, query)
  dsl = model.full_search_dsl
  config = dsl.highlight_config || { open_tag: "<mark>", close_tag: "</mark>" }
  match_expr = QueryParser.to_match_expression(QueryParser.parse(query))
  return [] if match_expr.empty?

  table = FullSearch::Index.fts_table_name(model)
  cols = dsl.fields.map(&:name)
  return [] if cols.empty?

  highlight_parts = cols.each_with_index.map do |col, idx|
    "highlight(#{table}, #{idx}, #{quote(config[:open_tag])}, #{quote(config[:close_tag])}) AS #{col}_snippet"
  end.join(", ")

  sql = <<~SQL
    SELECT rowid, #{highlight_parts}
    FROM #{table}
    WHERE #{table} MATCH #{quote(match_expr)}
  SQL

  connection.execute(sql)
end

.manual_field_snippets(records, model, query) ⇒ Object



68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/full_search/highlighter.rb', line 68

def self.manual_field_snippets(records, model, query)
  dsl = model.full_search_dsl
  config = dsl.highlight_config || { open_tag: "<mark>", close_tag: "</mark>" }
  fields = dsl.fields

  records.to_h do |record|
    snippets = fields.each_with_object({}) do |field, hash|
      value = record.full_search_text_for(field.name).to_s
      highlighted = manual_highlight(value, query, config)
      key = field.as || field.name
      hash[key] = highlighted if highlighted.include?(config[:open_tag])
    end
    [record.id, snippets]
  end
end

.manual_highlight(text, query, config) ⇒ Object



108
109
110
111
# File 'lib/full_search/highlighter.rb', line 108

def self.manual_highlight(text, query, config)
  return text if text.empty? || query.empty?
  text.gsub(/#{Regexp.escape(query)}/i, "#{config[:open_tag]}\\0#{config[:close_tag]}")
end

.manual_snippets(records, model, query) ⇒ Object



56
57
58
59
60
61
62
63
64
65
66
# File 'lib/full_search/highlighter.rb', line 56

def self.manual_snippets(records, model, query)
  dsl = model.full_search_dsl
  config = dsl.highlight_config || { open_tag: "<mark>", close_tag: "</mark>" }
    cols = dsl.fields.map(&:name)

    records.to_h do |record|
      text = cols.map { |col| record.full_search_text_for(col).to_s }.join(" ").strip
      highlighted = manual_highlight(text, query, config)
      [record.id, highlighted.presence]
    end
end

.quote(value) ⇒ Object



140
141
142
# File 'lib/full_search/highlighter.rb', line 140

def self.quote(value)
  connection.quote(value)
end