Skip to content

Commit

Permalink
Add feature to customize highlight columns
Browse files Browse the repository at this point in the history
  • Loading branch information
mmarusyk committed Aug 23, 2023
1 parent 2ed9190 commit cec7c6a
Show file tree
Hide file tree
Showing 7 changed files with 121 additions and 8 deletions.
31 changes: 31 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -840,6 +840,37 @@ See the
[documentation](https://www.postgresql.org/docs/current/static/textsearch-controls.html)
for details on the meaning of each option.

Use the `columns` option to create `pg_search_<custom_name>_highlight` with the necessary columns.


```ruby
class Person < ActiveRecord::Base
include PgSearch::Model
pg_search_scope :search,
against: [:name, :bio],
using: {
tsearch: {
highlight: {
columns: {
first: :portrait,
second: [:portrait, :bio]
}
}
}
}
end

Person.create!(
:portrait => "He's driven by an innate curiosity to discover every corner of Alberta.",
:bio => "Born in rural Alberta, where the buffalo roam."
)

first_match = Person.search("Alberta").with_pg_search_highlight.first
first_match.pg_search_first_highlight => "He's driven by an innate curiosity to discover every corner of <b>Alberta</b>."
first_match.pg_search_second_highlight => "He's driven by an innate curiosity to discover every corner of <b>Alberta</b>. Born in rural <b>Alberta</b>, where the buffalo roam."
```


#### :dmetaphone (Double Metaphone soundalike search)

[Double Metaphone](http://en.wikipedia.org/wiki/Double_Metaphone) is an
Expand Down
14 changes: 14 additions & 0 deletions lib/pg_search.rb
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,20 @@ def message
"to access the pg_search_highlight attribute on returned records"
end
end

class PgSearchHighlightCustomColumnsNotSelected < StandardError
attr_reader :attribute

def initialize(attribute)
@attribute = attribute
end

def message
"You must chain .with_pg_search_highlight after the pg_search_scope " \
"and define highlight: { columns: } option in pg_search_scope " \
"to access the #{attribute} attribute on returned records"
end
end
end

require "pg_search/railtie" if defined?(Rails::Railtie)
4 changes: 3 additions & 1 deletion lib/pg_search/features/feature.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ def self.valid_options
%i[only sort_only]
end

attr_reader :options

delegate :connection, :quoted_table_name, to: :@model

def initialize(query, options, all_columns, model, normalizer)
Expand All @@ -22,7 +24,7 @@ def initialize(query, options, all_columns, model, normalizer)

private

attr_reader :query, :options, :all_columns, :model, :normalizer
attr_reader :query, :all_columns, :model, :normalizer

def document
columns.map(&:to_sql).join(" || ' ' || ")
Expand Down
20 changes: 16 additions & 4 deletions lib/pg_search/features/tsearch.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,21 +20,33 @@ def rank
arel_wrap(tsearch_rank)
end

def highlight
arel_wrap(ts_headline)
def highlight(hl_columns = [])
arel_wrap(ts_headline(hl_columns))
end

private

def ts_headline
def ts_headline(hl_columns)
Arel::Nodes::NamedFunction.new("ts_headline", [
dictionary,
arel_wrap(document),
arel_wrap(document(hl_columns)),
arel_wrap(tsquery),
Arel::Nodes.build_quoted(ts_headline_options)
]).to_sql
end

def document(hl_columns)
selected_columns(hl_columns).map(&:to_sql).join(" || ' ' || ")
end

def selected_columns(hl_columns)
return columns if hl_columns.blank?

columns.select do |column|
Array.wrap(hl_columns).map(&:to_s).include? column.name
end
end

def ts_headline_options
return "" unless options[:highlight].is_a?(Hash)

Expand Down
4 changes: 4 additions & 0 deletions lib/pg_search/model.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ def method_missing(symbol, *args)
raise PgSearchHighlightNotSelected unless respond_to?(:pg_search_highlight)

read_attribute(:pg_search_highlight)
when /^pg_search_(.+)_highlight$/
raise PgSearchHighlightCustomColumnsNotSelected.new(symbol) unless respond_to?(symbol)

read_attribute(symbol)
else
super
end
Expand Down
22 changes: 19 additions & 3 deletions lib/pg_search/scope_options.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,27 @@ def tsearch
def with_pg_search_highlight
scope = self
scope = scope.select("#{table_name}.*") unless scope.select_values.any?
scope.select("(#{highlight}) AS pg_search_highlight")
add_highlight_columns(scope)
end

def highlight
tsearch.highlight.to_sql
def add_highlight_columns(scope)
if highlight_columns.present?
highlight_columns.each do |name, cols|
scope = scope.select("(#{highlight(cols)}) AS pg_search_#{name}_highlight")
end

scope
else
scope.select("(#{highlight}) AS pg_search_highlight")
end
end

def highlight(columns = [])
tsearch.highlight(columns).to_sql
end

def highlight_columns
tsearch.options.dig(:highlight, :columns)
end
end

Expand Down
34 changes: 34 additions & 0 deletions spec/integration/pg_search_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -706,6 +706,40 @@
expect(result.pg_search_highlight).to eq(%(<mark class="highlight">Let</mark> text<delim class="my_delim"><mark class="highlight">Let</mark> text))
end
end

context "with highlighting columns options" do
before do
ModelWithPgSearch.create!(
title: "The cat reads under the tree.",
content: "He hides behind a tree with colorful leaves."
)

ModelWithPgSearch.pg_search_scope :search_content,
against: %i[title content],
using: {
tsearch: {
highlight: {
columns: {
first: :title,
second: %i[title content]
}
}
}
}
end

it "adds a #pg_search_first_highlight method to each returned model record" do
result = ModelWithPgSearch.search_content("tree").with_pg_search_highlight.first

expect(result.pg_search_first_highlight).to eq(%(The cat reads under the <b>tree</b>.))
end

it "adds a #pg_search_second_highlight method to each returned model record" do
result = ModelWithPgSearch.search_content("tree").with_pg_search_highlight.first

expect(result.pg_search_second_highlight).to eq(%(The cat reads under the <b>tree</b>. He hides behind a <b>tree</b> with colorful leaves.))
end
end
end

describe "ranking" do
Expand Down

0 comments on commit cec7c6a

Please sign in to comment.