dingus

/ˈdɪŋəs/ Something whose name is unknown or forgotten

Highlighting search results with Textacular

May 2022

It’s possible to leverage Postgres’s ts_headline1 to add highlighting of matched fragments in search results. With a caveat, the search fields need to be declared ahead of time.

To highlight a basic_search each column to be highlighted needs to be SELECT-ed using ts_headline with the query string parsed with plainto_tsquery.

class Post < ApplicationRecord
  def self.search(query)
    basic_search(title: query)
      .select("#{sanitize_sql_array(["ts_headline(title, plainto_tsquery(?))", query])} as title_highlighted")
  end
end

Using this blog’s post titles for testing we see:

[0] pry(main)> Post.search("server Rack").map(&:attributes)
  Post Load (3.1ms)  SELECT "posts".*, COALESCE(ts_rank(to_tsvector('english', "posts"."title"::text), plainto_tsquery('english', 'server\ Rack'::text)), 0) AS "rank35483819984107261", ts_headline(title, plainto_tsquery('server Rack')) as title_highlighted FROM "posts" WHERE (to_tsvector('english', "posts"."title"::text) @@ plainto_tsquery('english', 'server\ Rack'::text)) ORDER BY "rank35483819984107261" DESC
=> [{"id"=>4,
  "title"=>"Server-sent events with Rails and Rack hijack",
  "created_at"=>Mon, 30 May 2022 17:30:29.806047000 UTC +00:00,
  "updated_at"=>Mon, 30 May 2022 17:30:29.806047000 UTC +00:00,
  "rank35483819984107261"=>0.085297264,
  "title_highlighted"=>"<b>Server</b>-sent events with Rails and <b>Rack</b> hijack"}]

To work for an advanced_search the query string should be passed to to_tsquery and web_search to websearch_to_tsquery.