Skip to content

Commit

Permalink
Store Alchemy::Page as a single PgSearch::Document
Browse files Browse the repository at this point in the history
Previously the page and the ingredients (and in previous versions also essences) were stored as separate PgSearch::Document entries. On search these document were combined to a single document to make it usable in the search itself. These mechanic made it pretty difficult to extend the search with other models. In newer versions of Alchemy the page is only available after a new page version is released and these mechanic is used to create a single PgSearch::Document with the content of the page and all supported ingredients. This change makes the whole search and the index creation less complex.
  • Loading branch information
sascha-karnatz committed Oct 9, 2024
1 parent 7418988 commit 48d79bc
Show file tree
Hide file tree
Showing 10 changed files with 97 additions and 260 deletions.
27 changes: 10 additions & 17 deletions app/extensions/alchemy/pg_search/ingredient_extension.rb
Original file line number Diff line number Diff line change
@@ -1,18 +1,15 @@
module Alchemy::PgSearch::IngredientExtension
def searchable_content
content_field = case type
when "Alchemy::Ingredients::Richtext"
:stripped_body
when "Alchemy::Ingredients::Picture"
:caption
else
:value
end

def self.multisearch_config
{
against: [
:value,
],
additional_attributes: ->(ingredient) { { page_id: ingredient.element.page.id } },
if: :searchable?
}
end

def self.prepended(base)
base.include PgSearch::Model
base.multisearchable(multisearch_config)
send(content_field)
end

def searchable?
Expand All @@ -24,7 +21,3 @@ def searchable?

# add the PgSearch model to all ingredients
Alchemy::Ingredient.prepend(Alchemy::PgSearch::IngredientExtension)

# add custom content fields for Richtext, and Picture
Alchemy::Ingredients::Picture.multisearchable(Alchemy::PgSearch::IngredientExtension.multisearch_config.merge({against: [:caption]}))
Alchemy::Ingredients::Richtext.multisearchable(Alchemy::PgSearch::IngredientExtension.multisearch_config.merge({against: [:stripped_body]}))
14 changes: 0 additions & 14 deletions app/extensions/alchemy/pg_search/pg_search_document_extension.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,6 @@ module Alchemy::PgSearch::PgSearchDocumentExtension
def self.prepended(base)
base.belongs_to :page, class_name: "::Alchemy::Page", foreign_key: "page_id", optional: true
end

##
# get a list of excerpts of the searched phrase
# The JSON_AGG - method will transform the grouped content entries into json which have to be "unpacked".
# @return [array<string>]
def excerpts
return [] if content.blank?
begin
parsed_content = JSON.parse content
parsed_content.kind_of?(Array) ? parsed_content : []
rescue JSON::ParserError
[]
end
end
end

PgSearch::Document.prepend(Alchemy::PgSearch::PgSearchDocumentExtension)
8 changes: 1 addition & 7 deletions app/views/alchemy/search/_result.html.erb
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@
<li class="search_result">
<% page = result.page %>
<h3><%= link_to page.name, show_alchemy_page_path(page) %></h3>
<% if result.excerpts.any? %>
<% result.excerpts.each do |excerpt| %>
<p><%= highlighted_excerpt(excerpt, params[:query]) %></p>
<% end %>
<% else %>
<p><%= page.meta_description %></p>
<% end %>
<p><%= highlighted_excerpt(result.content, params[:query]) %></p>
<p><%= link_to page.urlname, show_alchemy_page_path(page) %></p>
</li>
24 changes: 11 additions & 13 deletions lib/alchemy-pg_search.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,17 @@ def self.is_searchable?(ingredient_type)
end

##
# index all supported Alchemy models
# index all supported Alchemy pages
def self.rebuild
[Alchemy::Page, Alchemy::Ingredient].each do |model|
::PgSearch::Multisearch.rebuild(model)
end
Alchemy::Page.all.each{ |page| index_page(page) }
end

##
# remove the whole index for the page
#
# @param page [Alchemy::Page]
def self.remove_page(page)
::PgSearch::Document.delete_by(page_id: page.id)
::PgSearch::Document.delete_by(searchable_type: "Alchemy::Page", searchable_id: page.id)
end

##
Expand All @@ -41,12 +39,14 @@ def self.remove_page(page)
def self.index_page(page)
remove_page page

page.update_pg_search_document
document = page.update_pg_search_document
page.all_elements.includes(:ingredients).find_each do |element|
element.ingredients.select { |i| Alchemy::PgSearch.is_searchable?(i.type) }.each do |ingredient|
ingredient.update_pg_search_document
element.ingredients.select { |i| i.searchable? }.each do |ingredient|
document.content += " #{ingredient.searchable_content}"
end
end
document.content.strip!
document.save
end

##
Expand All @@ -57,12 +57,10 @@ def self.index_page(page)
# @return [ActiveRecord::Relation]
def self.search(query, ability: nil)
query = ::PgSearch.multisearch(query)
.select("JSON_AGG(content) as content", :page_id)
.reorder("")
.group(:page_id)
.joins(:page)

query = query.merge(Alchemy::Page.accessible_by(ability, :read)) if ability
if ability
query = query.joins(:page).merge(Alchemy::Page.accessible_by(ability, :read))
end

query
end
Expand Down
4 changes: 2 additions & 2 deletions spec/dummy/config/alchemy/elements.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,11 @@
searchable: false
- role: public
type: Richtext
default: "This is some public text."
default: "This is some <i>public</i> richtext."
- role: confidential
type: Richtext
searchable: false
default: "This is some confidential text."
default: "This is some <i>confidential</i> richtext."
- role: image
type: Picture
- role: secret_image
Expand Down
6 changes: 6 additions & 0 deletions spec/dummy/config/alchemy/page_layouts.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@
- article
- secrets

- name: mixed
elements:
- mixed
autogenerate:
- mixed

- name: search
searchresults: true
unique: true
Expand Down
12 changes: 9 additions & 3 deletions spec/features/fulltext_search_feature_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,19 @@
image.save
end

before do
Alchemy::PgSearch.rebuild
end

it "displays search results from text ingredients" do
visit("/suche?query=search")
visit("/suche?query=headline")
within(".search_results") do
expect(page).to have_content("This is a headline everybody should be able to search for.")
end
end

it "displays search results from richtext essences" do
visit("/suche?query=search")
it "displays search results from richtext ingredient" do
visit("/suche?query=text%20block")
within(".search_results") do
expect(page).to have_content("This is a text block everybody should be able to search for.")
end
Expand Down Expand Up @@ -131,6 +135,7 @@

before do
nested_element.ingredient_by_role("headline").update!({ value: "Content from nested element" })
Alchemy::PgSearch.rebuild
end

it "displays search results from nested elements" do
Expand Down Expand Up @@ -184,6 +189,7 @@
page_version: create(:alchemy_page, :public).public_version,
)
end
Alchemy::PgSearch.rebuild
end

context "when default config is used" do
Expand Down
Loading

0 comments on commit 48d79bc

Please sign in to comment.