From 23d9929c7714dde5fda8cecc0ed72f7c1830ea08 Mon Sep 17 00:00:00 2001 From: Sascha Karnatz Date: Tue, 19 Dec 2023 18:32:44 +0100 Subject: [PATCH] Improve the search speed Add tsvactor_column to store the tsvector - content in the database. This speeds up the search speed on large table, because these value isn't calculated on fly anymore. A dictionary configuration to allow to setup the used PostgreSQL dictionary. --- README.md | 27 ++++++++++++++----- .../20231218165617_add_searchable_content.rb | 16 +++++++++++ lib/alchemy/pg_search/config.rb | 1 + lib/alchemy/pg_search/engine.rb | 6 ++++- .../20231218165617_add_searchable_content.rb | 16 +++++++++++ spec/dummy/db/schema.rb | 3 ++- 6 files changed, 61 insertions(+), 8 deletions(-) create mode 100644 db/migrate/20231218165617_add_searchable_content.rb create mode 100644 spec/dummy/db/migrate/20231218165617_add_searchable_content.rb diff --git a/README.md b/README.md index e7839a2..54f02a9 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,12 @@ Run install script: $ bin/rails g alchemy:pg_search:install ``` +> [!NOTE] +> The installation will generate an autogenerated column which can be configured with a language. The default +> value should work for the most languages. If you wish to change this behavior you should add +> the `Alchemy::PgSearch.config` +> into the project initializer before running the install script. (See [Configure Behavior](#configure-behavior)) + ## Usage Every `Ingredient` will be indexed unless you tell Alchemy to not index a specific content. @@ -88,10 +94,16 @@ Configure the gem in an initializer. The default configurations are: ```ruby Alchemy::PgSearch.config = { - paginate_per: 10, # amount of results per page + dictionary: 'simple', + paginate_per: 10 } ``` + Configuration Name | Default Value | Description +--------------------|---------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + dictionary | simple | Dictionary for the multisearch tsearch - config, which is used to find the content. The dictionary can impact the amout found documents, because it removes language specific stop words. See more in the [PostgreSQL documentation](https://www.postgresql.org/docs/current/textsearch-dictionaries.html). + paginate_per | 10 | Amount of results per page. The value can be set to `nil` to disable the pagination. + You can also overwrite the default multisearch configuration to use other search strategies. For more information take a look into the [PgSearch Readme](https://github.com/Casecommons/pg_search#configuring-multi-search). @@ -107,7 +119,8 @@ end ### Rendering search results. -In order to render the search results, you'll need a page layout that represents the search result page. Simply mark a page layout as `searchresults: true`. The search form will pick this page as result page. +In order to render the search results, you'll need a page layout that represents the search result page. Simply mark a +page layout as `searchresults: true`. The search form will pick this page as result page. #### Search Results Page @@ -118,7 +131,8 @@ In order to render the search results, you'll need a page layout that represents unique: true ``` -Tip: For maximum flexibility you could also add an element that represents the search results. This lets your editors to place additional elements (maybe a header image or additional text blocks) on the search result page. +Tip: For maximum flexibility you could also add an element that represents the search results. This lets your editors to +place additional elements (maybe a header image or additional text blocks) on the search result page. ```yaml # page_layouts.yml @@ -126,16 +140,17 @@ Tip: For maximum flexibility you could also add an element that represents the s searchresults: true unique: true elements: - - searchresults + - searchresults autogenerate: - - searchresults + - searchresults # elements.yml - name: searchresults unique: true ``` -and then use the view helpers to render the search form on the page layout partial and the search results on the element view partial. +and then use the view helpers to render the search form on the page layout partial and the search results on the element +view partial. ### View Helpers diff --git a/db/migrate/20231218165617_add_searchable_content.rb b/db/migrate/20231218165617_add_searchable_content.rb new file mode 100644 index 0000000..feb5996 --- /dev/null +++ b/db/migrate/20231218165617_add_searchable_content.rb @@ -0,0 +1,16 @@ +class AddSearchableContent < ActiveRecord::Migration[6.0] + def up + unless column_exists? :pg_search_documents, :searchable_content + execute %|ALTER TABLE pg_search_documents + ADD COLUMN searchable_content tsvector GENERATED ALWAYS AS ( + to_tsvector('#{Alchemy::PgSearch.config[:dictionary]}', coalesce(content, '')) + ) STORED;| + end + end + + def down + if column_exists? :pg_search_documents, :searchable_content + remove_column :pg_search_documents, :searchable_content + end + end +end diff --git a/lib/alchemy/pg_search/config.rb b/lib/alchemy/pg_search/config.rb index e51cea5..fcd8719 100644 --- a/lib/alchemy/pg_search/config.rb +++ b/lib/alchemy/pg_search/config.rb @@ -2,6 +2,7 @@ module Alchemy module PgSearch module Config @@config = { + dictionary: 'simple', paginate_per: 10 } diff --git a/lib/alchemy/pg_search/engine.rb b/lib/alchemy/pg_search/engine.rb index 52869e3..c12f013 100644 --- a/lib/alchemy/pg_search/engine.rb +++ b/lib/alchemy/pg_search/engine.rb @@ -24,7 +24,11 @@ class Engine < ::Rails::Engine # @link https://github.com/Casecommons/pg_search#searching-using-different-search-features ::PgSearch.multisearch_options = { using: { - tsearch: { prefix: true } + tsearch: { + prefix: true, + dictionary: Alchemy::PgSearch.config[:dictionary], + tsvector_column: 'searchable_content' + } } } end diff --git a/spec/dummy/db/migrate/20231218165617_add_searchable_content.rb b/spec/dummy/db/migrate/20231218165617_add_searchable_content.rb new file mode 100644 index 0000000..feb5996 --- /dev/null +++ b/spec/dummy/db/migrate/20231218165617_add_searchable_content.rb @@ -0,0 +1,16 @@ +class AddSearchableContent < ActiveRecord::Migration[6.0] + def up + unless column_exists? :pg_search_documents, :searchable_content + execute %|ALTER TABLE pg_search_documents + ADD COLUMN searchable_content tsvector GENERATED ALWAYS AS ( + to_tsvector('#{Alchemy::PgSearch.config[:dictionary]}', coalesce(content, '')) + ) STORED;| + end + end + + def down + if column_exists? :pg_search_documents, :searchable_content + remove_column :pg_search_documents, :searchable_content + end + end +end diff --git a/spec/dummy/db/schema.rb b/spec/dummy/db/schema.rb index 619fbb5..805ca13 100644 --- a/spec/dummy/db/schema.rb +++ b/spec/dummy/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.0].define(version: 2023_07_24_074129) do +ActiveRecord::Schema[7.0].define(version: 2023_12_18_165617) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -376,6 +376,7 @@ t.datetime "created_at", null: false t.datetime "updated_at", null: false t.bigint "page_id" + t.virtual "searchable_content", type: :tsvector, as: "to_tsvector('simple'::regconfig, COALESCE(content, ''::text))", stored: true t.index ["page_id"], name: "index_pg_search_documents_on_page_id" t.index ["searchable_type", "searchable_id"], name: "index_pg_search_documents_on_searchable_type_and_searchable_id" end