Last active
July 20, 2020 19:23
-
-
Save copiousfreetime/6c10110752a596bb612ec9cc1e887531 to your computer and use it in GitHub Desktop.
Pg Search Snippets
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class CreatePgSearchDocuments < ActiveRecord::Migration[6.0] | |
def self.up | |
create_table :pg_search_documents do |t| | |
t.text :content | |
t.string :searchable_type | |
t.uuid :searchable_id | |
t.tsvector :content_tsvector | |
t.timestamps null: false | |
end | |
add_index :pg_search_documents, [:searchable_type, :searchable_id] | |
# index for tsearch query improvment | |
add_index :pg_search_documents, :content_tsvector, using: 'gin' | |
execute <<~SQL | |
CREATE INDEX pg_search_documents_on_content ON pg_search_documents USING gin(coalesce(content, ''::text) gin_trgm_ops) | |
SQL | |
create_trigger("pg_search_documents_tsearch_tr", compatibility: 1) | |
.on(:pg_search_documents) | |
.before(:insert, :update) | |
.for_each(:row) do | |
"new.content_tsvector := to_tsvector('simple', coalesce(new.content::text, ''))" | |
end | |
end | |
def self.down | |
drop_table :pg_search_documents | |
# trigger function is last | |
drop_trigger("pg_search_documents_tsearch_tr", compatibility: 1) | |
end | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Top level setup - we are using the `Multi-search approache | |
# https://github.com/Casecommons/pg_search#multi-search which requries a table | |
# specificically dedicated to the search index. And migration to set it up. | |
# | |
# We are also using explicit ts_vector colums so the content isn't stemmed on | |
# every request | |
# | |
# https://github.com/Casecommons/pg_search#using-tsvector-columns | |
# | |
# | |
# Model concern in all the models that are searchable so if need be we can just | |
# search for that model | |
# | |
module Searchable | |
extend ActiveSupport::Concern | |
# Helper method so that Models can search just themselves from within the | |
# multisearch index. This is syntactic-sugar around the global | |
# PgSearch.multisearch(q) + document.searchable to get the original Model | |
# objects | |
class_methods do | |
def search(q) | |
search_scope = PgSearch.multisearch(q).where(searchable_type: self.name) | |
# Explicitly select the type, id and rank so we can use them in the join | |
# with the main model table | |
search_scope = search_scope.select("searchable_type, searchable_id, rank") | |
# use the above search_scope as a join table so we can select all the | |
# models that match, and order them by the rank they match. | |
self.joins("JOIN (#{search_scope.to_sql}) as pg_search_t ON pg_search_t.searchable_id = #{self.table_name}.id") | |
.order(rank: :desc) | |
end | |
end | |
end | |
## Example Book model that is searchable and pulls in text from associated | |
## models so that those words will also find this book | |
# | |
class Book | |
include PgSearch::Model | |
multisearchable against: [ | |
:isbn, | |
:title, | |
:custom_searchable_text | |
] | |
include Searchable # the concern from above | |
# Additional in additional text to a bag of words field in this model - this | |
# cold also be done with `additional_attributes` field on the | |
# pg_search_documents table - but those are indexed, just additional static | |
# attribuetes | |
# | |
def custom_searchable_text | |
Array.new.tap do |a| | |
a << author.name | |
a << publisher.name | |
a << genres.pluck(:name) | |
end.join(" ") | |
end | |
end | |
# One thing of note if the author is updated, this will not update the search | |
# context on the book - so will need to have some post update triggers / jobs to update | |
# the index on the associated book if the author name is updated. | |
class Author | |
include PgSearch::Model | |
multisearchable against: [ | |
:name, | |
:bio, | |
:custom_searchable_text | |
] | |
include Searchable | |
def customs_searchable_text | |
Array.new.tap do |a| | |
a << books.pluck(:title) | |
end | |
end | |
end | |
# | |
# config/iniitalizers/pg_search.rb | |
# | |
# | |
PgSearch.multisearch_options = { | |
using: { | |
tsearch: { | |
prefix: true, # allow prefix's of words to be found also | |
negation: true, # allow !word syntax in the search query for negation | |
tsvector_column: "content_tsvector", # set the tsvector_column | |
}, | |
trigram: {} # activate trigram matching too | |
} | |
} | |
# | |
# Example Controllers using the Searchable concern to get back the original | |
# ActiveRecord objects | |
# | |
class BooksController | |
def search | |
# q can be anything and will match anything that is multisearchable against | |
# | |
search_query = params[:q] | |
books_scope = Book.search(search_query) | |
end | |
end | |
class AuthorsController | |
def search | |
search_query = params[:q] | |
authors_scope = Author.search(search_query) | |
end | |
end | |
# | |
# Example pan-site search controlelr to return multipel tyeps of active record | |
# objects from a single search query. | |
# | |
class SearchController | |
def search | |
search_query = params[:q] | |
documents = PgSearch.multisearch(search_query) | |
# Converting to the models | |
# | |
# Option 1: | |
# this will return all the PgSearch::Document instances which are associated | |
# to the upstream Models, you could inflate them all individualy by doing: | |
records = documents.map{ |d| d.searchable } # will make N db queries 1 per record | |
# Option 2: | |
# or inflate many by type, do this manually or maybe do it by passing | |
# through global id? this is totally untested and not sure if it preserves | |
# results order or not | |
global_ids = documents.map { |d| | |
GlobalID.initialize( | |
URI::GID.build(app: GlobalID.app,model_name: d.searchable_type, model_id: d.searchable_id) | |
) | |
} | |
records = GlobalID::Locator.locate_many(global_ids) | |
# Now display as approproate in your search view. | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment