This is a quick-n-dirty ActiveRecord module to quickly add full-text capabilities to a PostgreSQL table wihtout too much overhead.
Author: Ryan McGeary : http://github.com/rmm5t
License: MIT : https://rmm5t.mit-license.org/
This is a quick-n-dirty ActiveRecord module to quickly add full-text capabilities to a PostgreSQL table wihtout too much overhead.
Author: Ryan McGeary : http://github.com/rmm5t
License: MIT : https://rmm5t.mit-license.org/
# TL;DR: A poor-man's full-text search. | |
# | |
# This module employs a mechanism by which we can easily add left outer joins | |
# to queries (typically for full-text-like searches that need to dig into | |
# specific associations). | |
# | |
# One way to do this is to just use ActiveRecord's `eager_load` as part of a | |
# default scope, but this triggers multiple left outer joins for every single | |
# query, regardless of whether a search is being performed. | |
# | |
# These related associations are often displayed as part of any generic query, | |
# so a default scope that `includes` the associations often boosts performance | |
# and eliminates N+1 query problems. | |
# | |
# This module gives the best of both worlds. It makes it easy to set up a | |
# default scope using `includes`, but also provides a mechanism to easily | |
# perform an `eager_load` when necessary (using this module's custom | |
# `searching` criteria method). This works because combining `includes` with | |
# `references` gives the same behavior as `eager_load`. | |
# | |
# Usage: | |
# | |
# class Company < ActiveRecord::Base | |
# include PgSearchable | |
# self.searchable_includes = [:contacts, :assets] | |
# self.searchable_text = %w[companies.name contacts.name assets.name] | |
# self.searchable_literals = %w[contacts.email] | |
# end | |
# | |
# Company.search("john doe acme") | |
# Company.search("john@acme") | |
# | |
# Author: Ryan McGeary : http://github.com/rmm5t | |
# License: MIT : https://rmm5t.mit-license.org/ | |
module PgSearchable | |
extend ActiveSupport::Concern | |
included do | |
mattr_accessor :searchable_includes | |
mattr_accessor :searchable_text, default: ["''"] | |
mattr_accessor :searchable_literals, default: ["''"] | |
end | |
class_methods do | |
def searching(*args) | |
query = where(*args) | |
if searchable_includes.present? | |
query = query.includes(*searchable_includes) | |
query = query.references(*searchable_includes) if args.compact.present? | |
end | |
query | |
end | |
def search(query) | |
return searching(nil) if query.blank? | |
text = "array_to_string(Array[#{searchable_text.join(', ')}], ' ')" | |
literals = "array_to_string(Array[#{searchable_literals.join(', ')}], ' ')" | |
searching("(#{text}) @@ :q OR (#{literals}) ilike :word", q: query, word: "%#{query}%") | |
end | |
end | |
end |