Skip to content

Instantly share code, notes, and snippets.

@tasandberg
Last active May 27, 2021 21:40
Show Gist options
  • Save tasandberg/a393bdffceb9a72a1728e0b62cc8c70a to your computer and use it in GitHub Desktop.
Save tasandberg/a393bdffceb9a72a1728e0b62cc8c70a to your computer and use it in GitHub Desktop.
ActiveRecord workaround for pg_search and tsvector columns

Everything I found for optimizing pg text search performance via pg tsvector column suggested adding a postgres trigger, as described here

https://thoughtbot.com/blog/optimizing-full-text-search-with-postgres-tsvector-columns-and-triggers

class AddTsvectorColumns < ActiveRecord::Migration
  def up
    add_column :products, :tsv, :tsvector
    add_index :products, :tsv, using: "gin"

    execute <<-SQL
      CREATE TRIGGER tsvectorupdate BEFORE INSERT OR UPDATE
      ON products FOR EACH ROW EXECUTE PROCEDURE
      tsvector_update_trigger(
        tsv, 'pg_catalog.english', description, manufacturer_name, name
      );
    SQL

    now = Time.current.to_s(:db)
    update("UPDATE products SET updated_at = '#{now}'")
  end

  def down
    execute <<-SQL
      DROP TRIGGER tsvectorupdate
      ON products
    SQL

    remove_index :products, :tsv
    remove_column :products, :tsv
  end
end

My model was set up simply like so:

class Office < ApplicationRecord
  include PgSearch::Model

  multisearchable against: [:tsv]
  ...

After adding a create operation to my app I realized that the pg_search gem was rebuilding the search documents before the PG hook could set the tsv column properly. This resulted in the new record being unsearchable.

There are a lot of directions you could go in to skin this cat, but my solution was to do away with the PG trigger, and manually maintain the tsv column with an ActiveRecord hook on my searchable models:

So I removed the triggers from my db and updated my model like so:

class Office < ApplicationRecord
  include PgSearch::Model

  multisearchable against: [:tsv]

  before_save :update_tsvector

  private

  def update_tsvector
    # Create a string with the values of my searchable columns
    column_string = [name, city, state, street, zip].join(' ')
    
    # Query to use PG to generate the tsvector
    query = <<~SQL
      SELECT to_tsvector('english', \'#{column_string}\');
    SQL
    
    # Hit PG with the query to generate tsvector and save it on the tsv column before save
    self.tsv = ActiveRecord::Base.connection.execute(query.chomp).first.values[0]
  end
end

Of course you'd want to factor this hook out into a model concern, with a class method to defind searchable columns, and probably other optimizations, but this is the initial brain dump, so enjoy!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment