ruby 54 lines · 9 steps

Building search-result snippets in Ruby

A class that finds a query match in text and returns a trimmed, highlighted excerpt around it.

Explained by highlit
1class SnippetHighlighter
2 CONTEXT_RADIUS = 60
3 MAX_TERMS = 8
4 
5 def initialize(text, query)
6 @text = text.to_s
7 @terms = extract_terms(query)
8 end
9 
10 def call
11 return truncated_lead if @terms.empty?
12 
13 match = first_match
14 return truncated_lead unless match
15 
16 window = build_window(match.begin(0), match.end(0))
17 highlight(window)
18 end
19 
20 private
21 
22 def extract_terms(query)
23 query.to_s.scan(/\w+/).uniq.first(MAX_TERMS).map(&:downcase)
24 end
25 
26 def pattern
27 @pattern ||= Regexp.union(@terms.map { |t| /#{Regexp.escape(t)}/i })
28 end
29 
30 def first_match
31 pattern.match(@text)
32 end
33 
34 def build_window(match_start, match_end)
35 from = [match_start - CONTEXT_RADIUS, 0].max
36 to = [match_end + CONTEXT_RADIUS, @text.length].min
37 
38 from -= 1 until from.zero? || @text[from - 1] =~ /\s/
39 to += 1 until to == @text.length || @text[to] =~ /\s/
40 
41 prefix = from.zero? ? "" : "\u2026"
42 suffix = to == @text.length ? "" : "\u2026"
43 "#{prefix}#{@text[from...to].strip}#{suffix}"
44 end
45 
46 def highlight(window)
47 window.gsub(pattern) { |m| "<mark>#{m}</mark>" }
48 end
49 
50 def truncated_lead
51 lead = @text[0, CONTEXT_RADIUS * 2].to_s.strip
52 @text.length > lead.length ? "#{lead}\u2026" : lead
53 end
54end
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Clamping offsets with max and min keeps window boundaries safely inside the string.
  2. 2Regexp.union with escaped, case-insensitive terms matches any query word in one pass.
  3. 3Graceful fallbacks — an empty query or no match — keep a snippet builder from ever returning nothing useful.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Building search-result snippets in Ruby — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code