From 47beb3fe6ed6fdb75c2dd3a55362ad1aba3bbc3b Mon Sep 17 00:00:00 2001 From: erdgeist Date: Sat, 11 Jul 2026 23:41:29 +0200 Subject: Make page_translations' search trigger self-installing search_vector is populated by a raw Postgres trigger created via execute() in a migration -- invisible to schema.rb, which only represents structure Rails understands. Any database rebuilt from schema.rb rather than replayed migrations (test, a fresh db:setup, disaster recovery) silently lost full-text search entirely, with no error -- NULL @@ anything is never true in Postgres. Page.ensure_search_vector_trigger!, called from an after_initialize initializer, reinstalls the trigger via CREATE OR REPLACE on every boot, in every environment. Idempotent and cheap. --- app/models/page.rb | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) (limited to 'app') diff --git a/app/models/page.rb b/app/models/page.rb index 740d42e..db5b688 100644 --- a/app/models/page.rb +++ b/app/models/page.rb @@ -254,6 +254,32 @@ class Page < ApplicationRecord end + # Installs (or re-installs) the trigger that keeps page_translations' + # search_vector in sync. Idempotent, safe to call on every boot. + # search_vector is populated by a raw Postgres trigger, not anything + # Rails' schema dumper can represent -- a database rebuilt from + # schema.rb rather than replayed migrations silently loses it. + def self.ensure_search_vector_trigger! + connection.execute(<<~SQL) + CREATE OR REPLACE FUNCTION page_translations_search_vector_update() + RETURNS trigger AS $$ + BEGIN + NEW.search_vector := to_tsvector( + 'simple', + coalesce(NEW.title, '') || ' ' || + coalesce(NEW.abstract, '') || ' ' || + coalesce(NEW.body, '') + ); + RETURN NEW; + END; + $$ LANGUAGE plpgsql; + + CREATE OR REPLACE TRIGGER page_translations_search_vector_trigger + BEFORE INSERT OR UPDATE ON page_translations + FOR EACH ROW EXECUTE PROCEDURE page_translations_search_vector_update(); + SQL + end + private def set_page_title -- cgit v1.3