alchemy-pg_search 5.2.0 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bd79aeea752492e2162f8338e15726c51af184f2ee0025cffd6f7eb98914eb8c
4
- data.tar.gz: 5b8bd8894f18350baffdb0537bcbdebc9e781f8aa86251919972b6a076c3171f
3
+ metadata.gz: '009bedc83da970afa4e20ea64375695711b50ef37027b4e4cbe08a7add274070'
4
+ data.tar.gz: c324b6915704b0ef189d539510d57ff80962be5f6244907bbfd9d82e0d13eec8
5
5
  SHA512:
6
- metadata.gz: c0333bfcb6a05dc82aa69c7147429bbe337315d90c61bcbbf3ae6d4ef3d11b28137e934dde2d38f98ce02288dcfb4d08cadc099b6d2d998da0c69abe044d3089
7
- data.tar.gz: f1b6ac52bdfc1cb2199344eca5f691c03063b1d9d1a56d28236512621650640390adf093842c8111998992a26bfed2b2927360f2bddcbc286e9bc0910226ec5e
6
+ metadata.gz: 0eda59775b10f840a97362c244beb387e259e104d18f03e855984ea15437649d78c1a397a4a2f443e62c62bbd74f42288b77ce7ede6401d090ae28b1d3844d06
7
+ data.tar.gz: 6245d3b156a31fe0459fc9372de9c3ffc8ab7f123dd717402a7dc3aeb206876912069f88ae4d20a127039ee1754d45f87c6391633490f8363dde101a2cb8fb10
data/.gitignore CHANGED
@@ -7,6 +7,7 @@ spec/dummy/log/*.log
7
7
  spec/dummy/db/*.sqlite3*
8
8
  spec/dummy/tmp/*
9
9
  spec/dummy/public/*
10
+ spec/dummy/db/migrate/*
10
11
  spec/dummy/uploads/*
11
12
  .ruby-*
12
13
  node_modules
data/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # Changelog
2
2
 
3
+ ## [v6.0.0](https://github.com/AlchemyCMS/alchemy-pg_search/tree/v6.0.0) (2024-12-03)
4
+
5
+ [Full Changelog](https://github.com/AlchemyCMS/alchemy-pg_search/compare/v5.2.0...v6.0.0)
6
+
7
+ **Merged pull requests:**
8
+
9
+ - Use an inner select for CanCanCan abilities [\#61](https://github.com/AlchemyCMS/alchemy-pg_search/pull/61) ([sascha-karnatz](https://github.com/sascha-karnatz))
10
+ - Use public\_on instead of published\_at for pg\_search\_documents [\#60](https://github.com/AlchemyCMS/alchemy-pg_search/pull/60) ([sascha-karnatz](https://github.com/sascha-karnatz))
11
+ - Add searchable\_created\_at field [\#59](https://github.com/AlchemyCMS/alchemy-pg_search/pull/59) ([sascha-karnatz](https://github.com/sascha-karnatz))
12
+ - Add search page module and remove controller extension [\#58](https://github.com/AlchemyCMS/alchemy-pg_search/pull/58) ([sascha-karnatz](https://github.com/sascha-karnatz))
13
+ - Rearrange page search [\#57](https://github.com/AlchemyCMS/alchemy-pg_search/pull/57) ([sascha-karnatz](https://github.com/sascha-karnatz))
14
+ - Introduce a search\_class configuration into Alchemy [\#55](https://github.com/AlchemyCMS/alchemy-pg_search/pull/55) ([sascha-karnatz](https://github.com/sascha-karnatz))
15
+ - Remove search module [\#54](https://github.com/AlchemyCMS/alchemy-pg_search/pull/54) ([sascha-karnatz](https://github.com/sascha-karnatz))
16
+ - Use stripped\_body field for Richtext ingredient [\#51](https://github.com/AlchemyCMS/alchemy-pg_search/pull/51) ([sascha-karnatz](https://github.com/sascha-karnatz))
17
+ - Mark page id as optional on pg\_search\_document [\#50](https://github.com/AlchemyCMS/alchemy-pg_search/pull/50) ([sascha-karnatz](https://github.com/sascha-karnatz))
18
+ - Remove old migrations [\#49](https://github.com/AlchemyCMS/alchemy-pg_search/pull/49) ([sascha-karnatz](https://github.com/sascha-karnatz))
19
+ - Add view partial examples to README [\#48](https://github.com/AlchemyCMS/alchemy-pg_search/pull/48) ([sascha-karnatz](https://github.com/sascha-karnatz))
20
+ - Fix preview if search\_result\_page is nil [\#47](https://github.com/AlchemyCMS/alchemy-pg_search/pull/47) ([sascha-karnatz](https://github.com/sascha-karnatz))
21
+
3
22
  ## [v5.2.0](https://github.com/AlchemyCMS/alchemy-pg_search/tree/v5.2.0) (2024-02-05)
4
23
 
5
24
  [Full Changelog](https://github.com/AlchemyCMS/alchemy-pg_search/compare/v5.1.0...v5.2.0)
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  # Alchemy CMS Postgresql Fulltext Search
4
4
 
5
- This gem provides full text search for projects using postgresql databases to Alchemy CMS 6.0 and above.
5
+ This gem provides full text search for projects using postgresql databases to Alchemy CMS 7.0 and above.
6
6
 
7
7
  ## Requirements
8
8
 
@@ -135,16 +135,9 @@ In order to render the search results, you'll need a page layout that represents
135
135
  page layout as `searchresults: true`. The search form will pick this page as result page.
136
136
 
137
137
  #### Search Results Page
138
-
139
- ```yaml
140
- # page_layouts.yml
141
- - name: search
142
- searchresults: true
143
- unique: true
144
- ```
145
-
146
- Tip: For maximum flexibility you could also add an element that represents the search results. This lets your editors to
147
- place additional elements (maybe a header image or additional text blocks) on the search result page.
138
+
139
+ Add a search layout to the `page_layout.yml` and mark it with a `searchresults` flag. These flag is used to find the
140
+ correct page path for search form.
148
141
 
149
142
  ```yaml
150
143
  # page_layouts.yml
@@ -159,15 +152,20 @@ place additional elements (maybe a header image or additional text blocks) on th
159
152
  # elements.yml
160
153
  - name: searchresults
161
154
  unique: true
162
- ingredients:
163
- - role: search_string
164
- hint: The is only for presentational purposes in the Alchemy preview
165
- default: lorem
166
- type: Text
167
155
  ```
168
156
 
169
- and then use the view helpers to render the search form on the page layout partial and the search results on the element
170
- view partial.
157
+ and then use the view helpers to render the search form on the page layout partial.
158
+
159
+ ```erb
160
+ <!-- app/views/alchemy/page_layouts/_search.html.erb -->
161
+ <%= render_elements %>
162
+
163
+ <!-- app/views/alchemy/elements/_searchresults.html.erb -->
164
+ <%= element_view_for(searchresults) do |el| -%>
165
+ <% search_results = Alchemy::Search::SearchPage.perform_search(params, ability: current_ability) %>
166
+ <%= render "alchemy/search/results", search_results: search_results %>
167
+ <% end %>
168
+ ```
171
169
 
172
170
  ### View Helpers
173
171
 
@@ -176,9 +174,6 @@ This gem provides some helper methods that let you render the form and the searc
176
174
  * Render the search form:
177
175
  `render_search_form`
178
176
 
179
- * Render the search results:
180
- `render_search_results`
181
-
182
177
  ### Customize Views
183
178
 
184
179
  If you want to override the search form and search result views please use this generator.
@@ -225,7 +220,7 @@ and reindex your database in your Rails console
225
220
 
226
221
  ```rb
227
222
  # rails console
228
- $ Alchemy::PgSearch::Search.rebuild
223
+ $ Alchemy::PgSearch.rebuild
229
224
  ```
230
225
 
231
226
  ## Contributing
@@ -10,7 +10,7 @@ module Alchemy::PgSearch::PageExtension
10
10
  :meta_keywords,
11
11
  :name,
12
12
  ],
13
- additional_attributes: ->(page) { { page_id: page.id } },
13
+ additional_attributes: ->(page) { { page_id: page.id, searchable_created_at: page.public_on } },
14
14
  if: :searchable?,
15
15
  )
16
16
  end
@@ -23,7 +23,7 @@ module Alchemy::PgSearch::PageExtension
23
23
  private
24
24
 
25
25
  def remove_unpublished_page
26
- Alchemy::PgSearch::Search.remove_page(self) unless searchable?
26
+ Alchemy::PgSearch.remove_page(self) unless searchable?
27
27
  end
28
28
  end
29
29
 
@@ -1,20 +1,6 @@
1
1
  module Alchemy::PgSearch::PgSearchDocumentExtension
2
2
  def self.prepended(base)
3
- base.belongs_to :page, class_name: "::Alchemy::Page", foreign_key: "page_id"
4
- end
5
-
6
- ##
7
- # get a list of excerpts of the searched phrase
8
- # The JSON_AGG - method will transform the grouped content entries into json which have to be "unpacked".
9
- # @return [array<string>]
10
- def excerpts
11
- return [] if content.blank?
12
- begin
13
- parsed_content = JSON.parse content
14
- parsed_content.kind_of?(Array) ? parsed_content : []
15
- rescue JSON::ParserError
16
- []
17
- end
3
+ base.belongs_to :page, class_name: "::Alchemy::Page", foreign_key: "page_id", optional: true
18
4
  end
19
5
  end
20
6
 
@@ -1,4 +1,4 @@
1
- module Alchemy::PgSearch::ElementExtension
1
+ module Alchemy::Search::ElementExtension
2
2
  def self.prepended(base)
3
3
  base.attr_writer :searchable
4
4
  end
@@ -12,4 +12,4 @@ module Alchemy::PgSearch::ElementExtension
12
12
  end
13
13
  end
14
14
 
15
- Alchemy::Element.prepend(Alchemy::PgSearch::ElementExtension)
15
+ Alchemy::Element.prepend(Alchemy::Search::ElementExtension)
@@ -0,0 +1,14 @@
1
+ module Alchemy::Search::IngredientExtension
2
+ def searchable_content
3
+ send(Alchemy.searchable_ingredients[type.to_sym])&.squish
4
+ end
5
+
6
+ def searchable?
7
+ Alchemy.searchable_ingredients.has_key?(type.to_sym) &&
8
+ (definition.key?(:searchable) ? definition[:searchable] : true) &&
9
+ !!element&.searchable?
10
+ end
11
+ end
12
+
13
+ # add the PgSearch model to all ingredients
14
+ Alchemy::Ingredient.prepend(Alchemy::Search::IngredientExtension)
@@ -0,0 +1,10 @@
1
+ # Enable Postgresql full text indexing.
2
+ #
3
+ module Alchemy::Search::PageExtension
4
+ def searchable?
5
+ (definition.key?(:searchable) ? definition[:searchable] : true) &&
6
+ searchable && public? && !layoutpage?
7
+ end
8
+ end
9
+
10
+ Alchemy::Page.prepend(Alchemy::Search::PageExtension)
@@ -29,20 +29,8 @@ module Alchemy
29
29
  class: "fulltext_search",
30
30
  id: "search",
31
31
  }
32
- render "alchemy/search/form", options: default_options.merge(options), search_result_page: search_result_page
33
- end
34
-
35
- # Renders the search results partial within +app/views/alchemy/search/_results.html+
36
- #
37
- # @option options show_result_count [Boolean] (true) Should the count of results be displayed or not?
38
- # @option options show_heading [Boolean] (true) Should the heading be displayed or not?
39
- #
40
- def render_search_results(options = {})
41
- default_options = {
42
- show_result_count: true,
43
- show_heading: true,
44
- }
45
- render "alchemy/search/results", options: default_options.merge(options)
32
+ search_result_page = Alchemy::Search::SearchPage.search_result_page
33
+ render "alchemy/search/form", options: default_options.merge(options), search_result_page:
46
34
  end
47
35
 
48
36
  def highlighted_excerpt(text, phrase, radius = 50)
@@ -2,7 +2,7 @@ module Alchemy
2
2
  module PgSearch
3
3
  class IndexPageJob < BaseJob
4
4
  def perform(page)
5
- Search.index_page(page)
5
+ PgSearch.index_page(page)
6
6
  end
7
7
  end
8
8
  end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Alchemy
4
+ module Search
5
+ module SearchPage
6
+ def self.perform_search(params, ability: nil)
7
+ search_results = Alchemy.search_class.search(params[:query], ability:)
8
+ search_results = search_results&.page(params[:page])&.per(paginate_per) if paginate_per.present?
9
+
10
+ # order the documents by searchable_created_at and use the ranking order as second order argument
11
+ if params[:sort] == "date"
12
+ search_results.order_values.unshift("pg_search_documents.searchable_created_at DESC")
13
+ end
14
+
15
+ search_results
16
+ end
17
+
18
+ def self.paginate_per
19
+ Alchemy::PgSearch.config[:paginate_per]
20
+ end
21
+
22
+ def self.search_result_page
23
+ @search_result_page ||= begin
24
+ page_layouts = PageLayout.all.select do |page_layout|
25
+ page_layout.key?(:searchresults) && page_layout[:searchresults].to_s.casecmp(true.to_s).zero?
26
+ end
27
+
28
+ if page_layouts.nil?
29
+ raise "No searchresults page layout found. Please add page layout with `searchresults: true` into your `page_layouts.yml` file."
30
+ end
31
+
32
+ page = Page.published.find_by(
33
+ page_layout: page_layouts.first["name"],
34
+ language_id: Language.current.id,
35
+ )
36
+ if page.nil?
37
+ logger.warn "\n++++++\nNo published search result page found. Please create one or publish your search result page.\n++++++\n"
38
+ end
39
+ page
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -1,12 +1,6 @@
1
1
  <li class="search_result">
2
- <% page = result.page %>
2
+ <% page = result.searchable %>
3
3
  <h3><%= link_to page.name, show_alchemy_page_path(page) %></h3>
4
- <% if result.excerpts.any? %>
5
- <% result.excerpts.each do |excerpt| %>
6
- <p><%= highlighted_excerpt(excerpt, params[:query]) %></p>
7
- <% end %>
8
- <% else %>
9
- <p><%= page.meta_description %></p>
10
- <% end %>
4
+ <p><%= highlighted_excerpt(result.content, params[:query]) %></p>
11
5
  <p><%= link_to page.urlname, show_alchemy_page_path(page) %></p>
12
6
  </li>
@@ -1,5 +1,5 @@
1
1
  <div class="search_results">
2
- <% if @search_results.blank? %>
2
+ <% if search_results.blank? %>
3
3
  <h2 class="no_search_results">
4
4
  <%= raw Alchemy.t('search_result_page.no_results', query: h(params[:query])) %>
5
5
  </h2>
@@ -7,14 +7,14 @@
7
7
  <h2 class="search_results_heading">
8
8
  <%= raw Alchemy.t("search_result_page.result_heading", query: h(params[:query])) %>
9
9
  <%= Alchemy.t("search_result_page.result_count",
10
- count: @search_results.try(:total_count) || @search_results.size) %>
10
+ count: search_results.try(:total_count) || search_results.size) %>
11
11
  </h2>
12
12
  <ul class="search_result_list">
13
- <%= render(partial: 'alchemy/search/result', collection: @search_results) %>
13
+ <%= render(partial: 'alchemy/search/result', collection: search_results) %>
14
14
  </ul>
15
15
  <% end %>
16
16
  </div>
17
17
 
18
- <% if @search_results.try(:total_pages) %>
19
- <%= paginate @search_results %>
18
+ <% if search_results.try(:total_pages) %>
19
+ <%= paginate search_results %>
20
20
  <% end %>
@@ -0,0 +1,6 @@
1
+ class AddDocumentCreatedAtToPgSearchDocuments < ActiveRecord::Migration[7.1]
2
+ def change
3
+ add_column :pg_search_documents, :searchable_created_at, :datetime, if_not_exists: true
4
+ add_index :pg_search_documents, :searchable_created_at, if_not_exists: true
5
+ end
6
+ end
@@ -11,10 +11,6 @@ module Alchemy
11
11
  require_dependency(c)
12
12
  end
13
13
 
14
- # We need to have the search methods present in all Alchemy controllers
15
- Alchemy::PagesController.send(:include, Alchemy::PgSearch::ControllerMethods)
16
- Alchemy::Admin::PagesController.send(:include, Alchemy::PgSearch::ControllerMethods)
17
-
18
14
  # In development environment, this runs on every code reload, so avoid multiple reindexing jobs
19
15
  unless Alchemy.publish_targets.map(&:name).include? 'Alchemy::PgSearch::IndexPageJob'
20
16
  # reindex the page after it was published
@@ -1,5 +1,5 @@
1
1
  module Alchemy
2
2
  module PgSearch
3
- VERSION = "5.2.0"
3
+ VERSION = "6.0.0"
4
4
  end
5
5
  end
@@ -1,19 +1,53 @@
1
1
  require "alchemy/pg_search/engine"
2
2
  require "alchemy/pg_search/config"
3
- require "alchemy/pg_search/search"
4
3
 
5
4
  module Alchemy
6
- module PgSearch
7
- SEARCHABLE_INGREDIENTS = %w[Text Richtext Picture]
5
+ mattr_accessor :search_class
6
+ @@search_class = PgSearch
7
+
8
+ mattr_accessor :searchable_ingredients
9
+ @@searchable_ingredients = {
10
+ "Alchemy::Ingredients::Text": :value,
11
+ "Alchemy::Ingredients::Headline": :value,
12
+ "Alchemy::Ingredients::Richtext": :stripped_body,
13
+ "Alchemy::Ingredients::Picture": :caption,
14
+ }
8
15
 
16
+ module PgSearch
9
17
  extend Config
10
18
 
11
19
  ##
12
- # is ingredient searchable?
13
- # @param ingredient_type [string]
14
- # @return [boolean]
15
- def self.is_searchable?(ingredient_type)
16
- SEARCHABLE_INGREDIENTS.include?(ingredient_type.gsub(/Alchemy::Ingredients::/, ""))
20
+ # index all supported Alchemy pages
21
+ def self.rebuild
22
+ ActiveRecord::Base.transaction do
23
+ ::PgSearch::Document.delete_all
24
+ Alchemy::Page.all.each{ |page| index_page(page) }
25
+ end
26
+ end
27
+
28
+ ##
29
+ # remove the whole index for the page
30
+ #
31
+ # @param page [Alchemy::Page]
32
+ def self.remove_page(page)
33
+ ::PgSearch::Document.delete_by(page_id: page.id)
34
+ end
35
+
36
+ ##
37
+ # index a single page and indexable ingredients
38
+ #
39
+ # @param page [Alchemy::Page]
40
+ def self.index_page(page)
41
+ page.update_pg_search_document
42
+
43
+ document = page.pg_search_document
44
+ return if document.nil?
45
+
46
+ ingredient_content = page.all_elements.includes(ingredients: {element: :page}).map do |element|
47
+ element.ingredients.select { |i| i.searchable? }.map(&:searchable_content).join(" ")
48
+ end.join(" ")
49
+
50
+ document.update_column(:content, "#{document.content} #{ingredient_content}".squish)
17
51
  end
18
52
 
19
53
  ##
@@ -23,13 +57,14 @@ module Alchemy
23
57
  # @param ability [nil|CanCan::Ability]
24
58
  # @return [ActiveRecord::Relation]
25
59
  def self.search(query, ability: nil)
26
- Search.search(query, ability: ability)
27
- end
60
+ query = ::PgSearch.multisearch(query).includes(:searchable)
28
61
 
29
- ##
30
- # index all supported Alchemy models
31
- def self.rebuild
32
- Search.rebuild
62
+ if ability
63
+ inner_ability_select = Alchemy::Page.select(:id).merge(Alchemy::Page.accessible_by(ability, :read))
64
+ query = query.where("page_id IS NULL OR page_id IN (#{inner_ability_select.to_sql})")
65
+ end
66
+
67
+ query
33
68
  end
34
69
  end
35
70
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: alchemy-pg_search
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.2.0
4
+ version: 6.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Thomas von Deyen
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-02-05 00:00:00.000000000 Z
11
+ date: 2024-12-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: alchemy_cms
@@ -115,13 +115,14 @@ files:
115
115
  - README.md
116
116
  - Rakefile
117
117
  - alchemy-pg_search.gemspec
118
- - app/controller/alchemy/pg_search/controller_methods.rb
119
- - app/extensions/alchemy/pg_search/element_extension.rb
120
- - app/extensions/alchemy/pg_search/ingredient_extension.rb
121
118
  - app/extensions/alchemy/pg_search/page_extension.rb
122
119
  - app/extensions/alchemy/pg_search/pg_search_document_extension.rb
120
+ - app/extensions/alchemy/search/element_extension.rb
121
+ - app/extensions/alchemy/search/ingredient_extension.rb
122
+ - app/extensions/alchemy/search/page_extension.rb
123
123
  - app/helpers/alchemy/pg_search/search_helper.rb
124
124
  - app/jobs/alchemy/pg_search/index_page_job.rb
125
+ - app/services/alchemy/search/search_page.rb
125
126
  - app/views/alchemy/search/_form.html.erb
126
127
  - app/views/alchemy/search/_result.html.erb
127
128
  - app/views/alchemy/search/_results.html.erb
@@ -129,16 +130,12 @@ files:
129
130
  - bin/rspec
130
131
  - config/locales/alchemy.search.de.yml
131
132
  - config/locales/alchemy.search.en.yml
132
- - db/migrate/20141211105526_add_searchable_to_alchemy_essence_texts.rb
133
- - db/migrate/20141211105942_add_searchable_to_alchemy_essence_richtexts.rb
134
- - db/migrate/20141211110126_add_searchable_to_alchemy_essence_pictures.rb
135
- - db/migrate/20210923081905_move_searchable_to_contents.rb
136
133
  - db/migrate/20220826125413_add_page_id_column_to_pg_search_documents.rb
137
134
  - db/migrate/20231218165617_add_searchable_content.rb
135
+ - db/migrate/20241105092236_add_document_created_at_to_pg_search_documents.rb
138
136
  - lib/alchemy-pg_search.rb
139
137
  - lib/alchemy/pg_search/config.rb
140
138
  - lib/alchemy/pg_search/engine.rb
141
- - lib/alchemy/pg_search/search.rb
142
139
  - lib/alchemy/pg_search/version.rb
143
140
  - lib/generators/alchemy/pg_search/install/install_generator.rb
144
141
  - lib/generators/alchemy/pg_search/views/views_generator.rb
@@ -163,7 +160,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
163
160
  version: '0'
164
161
  requirements:
165
162
  - PostgreSQL >= 12.x
166
- rubygems_version: 3.5.5
163
+ rubygems_version: 3.4.19
167
164
  signing_key:
168
165
  specification_version: 4
169
166
  summary: This gem provides PostgreSQL full text search to Alchemy
@@ -1,96 +0,0 @@
1
- module Alchemy
2
- module PgSearch
3
-
4
- # Provides full text search methods in your controller
5
- #
6
- module ControllerMethods
7
-
8
- # Adds a +before_action+ to your controller
9
- #
10
- def self.included(controller)
11
- controller.send(:before_action, :perform_search, only: :show)
12
- controller.send(:helper_method, :search_result_page)
13
- controller.send(:helper, Alchemy::PgSearch::SearchHelper)
14
- end
15
-
16
- private
17
-
18
- # Performs a full text search with +PgSearch+.
19
- #
20
- # Gets invoked everytime 'query' is given in params.
21
- #
22
- # This method only sets the +@search_results+ instance variable.
23
- #
24
- # You have to redirect to the search result page within a search form.
25
- #
26
- # === Alchemy provides a handy helper for rendering the search form:
27
- #
28
- # render_search_form
29
- #
30
- # === Note
31
- #
32
- # If in preview mode a fake search value "lorem" will be set.
33
- #
34
- # @see Alchemy::PagesHelper#render_search_form
35
- #
36
- def perform_search
37
- set_preview_query
38
- return if params[:query].blank?
39
- @search_results = search_results
40
- if paginate_per
41
- @search_results = @search_results.page(params[:page]).per(paginate_per)
42
- end
43
- end
44
-
45
- # Find Pages that have what is provided in "query" param with PgSearch
46
- #
47
- # @return [Array]
48
- #
49
- def search_results
50
- Alchemy::PgSearch.search params[:query], ability: current_ability
51
- end
52
-
53
- # A view helper that loads the search result page.
54
- #
55
- # @return [Alchemy::Page,nil]
56
- #
57
- def search_result_page
58
- @search_result_page ||= begin
59
- page = Page.published.find_by(
60
- page_layout: search_result_page_layout["name"],
61
- language_id: Language.current.id,
62
- )
63
- if page.nil?
64
- logger.warn "\n++++++\nNo published search result page found. Please create one or publish your search result page.\n++++++\n"
65
- end
66
- page
67
- end
68
- end
69
-
70
- def search_result_page_layout
71
- # search for page layout with the attribute `searchresults: true`
72
- page_layouts = PageLayout.all.select do |page_layout|
73
- page_layout.key?(:searchresults) && page_layout[:searchresults].to_s.casecmp(true.to_s).zero?
74
- end
75
- if page_layouts.nil?
76
- raise "No searchresults page layout found. Please add page layout with `searchresults: true` into your `page_layouts.yml` file."
77
- end
78
- page_layouts.first
79
- end
80
-
81
- def paginate_per
82
- Alchemy::PgSearch.config[:paginate_per]
83
- end
84
-
85
- private
86
-
87
- def set_preview_query
88
- if self.class == Alchemy::Admin::PagesController && params[:query].blank?
89
- element = search_result_page.draft_version.elements.named(:searchresults).first
90
-
91
- params[:query] = element&.value_for("search_string") || "lorem"
92
- end
93
- end
94
- end
95
- end
96
- end
@@ -1,29 +0,0 @@
1
- module Alchemy::PgSearch::IngredientExtension
2
-
3
- def self.multisearch_config
4
- {
5
- against: [
6
- :value,
7
- ],
8
- additional_attributes: ->(ingredient) { { page_id: ingredient.element.page.id } },
9
- if: :searchable?
10
- }
11
- end
12
-
13
- def self.prepended(base)
14
- base.include PgSearch::Model
15
- base.multisearchable(multisearch_config)
16
- end
17
-
18
- def searchable?
19
- Alchemy::PgSearch.is_searchable?(type) &&
20
- (definition.key?(:searchable) ? definition[:searchable] : true) &&
21
- value.present? && !!element&.searchable?
22
- end
23
- end
24
-
25
- # add the PgSearch model to all ingredients
26
- Alchemy::Ingredient.prepend(Alchemy::PgSearch::IngredientExtension)
27
-
28
- # only enable the search for Text, Richtext, and Picture
29
- Alchemy::Ingredients::Picture.multisearchable(Alchemy::PgSearch::IngredientExtension.multisearch_config.merge({against: [:caption]}))
@@ -1,5 +0,0 @@
1
- class AddSearchableToAlchemyEssenceTexts < ActiveRecord::Migration[6.1]
2
- def change
3
- add_column :alchemy_essence_texts, :searchable, :boolean, default: true, if_not_exists: true
4
- end
5
- end
@@ -1,5 +0,0 @@
1
- class AddSearchableToAlchemyEssenceRichtexts < ActiveRecord::Migration[6.1]
2
- def change
3
- add_column :alchemy_essence_richtexts, :searchable, :boolean, default: true, if_not_exists: true
4
- end
5
- end
@@ -1,5 +0,0 @@
1
- class AddSearchableToAlchemyEssencePictures < ActiveRecord::Migration[6.1]
2
- def change
3
- add_column :alchemy_essence_pictures, :searchable, :boolean, default: true, if_not_exists: true
4
- end
5
- end
@@ -1,45 +0,0 @@
1
- class MoveSearchableToContents < ActiveRecord::Migration[6.1]
2
- def change
3
- if column_exists? :alchemy_contents, :searchable
4
- return
5
- end
6
-
7
- add_column :alchemy_contents, :searchable, :boolean, default: true
8
-
9
- {
10
- Text: "texts",
11
- Richtext: "richtexts",
12
- Picture: "pictures",
13
- }.each do |klass, table|
14
- reversible do |dir|
15
- dir.up do
16
- Alchemy::Content.connection.execute <<~SQL
17
- UPDATE alchemy_contents
18
- SET searchable = alchemy_essence_#{table}.searchable
19
- FROM alchemy_essence_#{table}
20
- WHERE
21
- alchemy_contents.essence_type = 'Alchemy::Essence#{klass}'
22
- AND
23
- alchemy_contents.essence_id = alchemy_essence_#{table}.id
24
- SQL
25
- end
26
-
27
- dir.down do
28
- Alchemy::Content.connection.execute <<~SQL
29
- UPDATE alchemy_essence_#{table}
30
- SET searchable = alchemy_contents.searchable
31
- FROM alchemy_contents
32
- WHERE
33
- alchemy_contents.essence_type = 'Alchemy::Essence#{klass}'
34
- AND
35
- alchemy_contents.essence_id = alchemy_essence_#{table}.id
36
- SQL
37
- end
38
- end
39
-
40
- remove_column "alchemy_essence_#{table}", :searchable, default: true
41
- end
42
-
43
- change_column_null :alchemy_contents, :searchable, false, true
44
- end
45
- end
@@ -1,55 +0,0 @@
1
- module Alchemy
2
- module PgSearch
3
- module Search
4
-
5
- ##
6
- # index all supported Alchemy models
7
- def self.rebuild
8
- [Alchemy::Page, Alchemy::Ingredient].each do |model|
9
- ::PgSearch::Multisearch.rebuild(model)
10
- end
11
- end
12
-
13
- ##
14
- # remove the whole index for the page
15
- #
16
- # @param page [Alchemy::Page]
17
- def self.remove_page(page)
18
- ::PgSearch::Document.delete_by(page_id: page.id)
19
- end
20
-
21
- ##
22
- # index a single page and indexable ingredients
23
- #
24
- # @param page [Alchemy::Page]
25
- def self.index_page(page)
26
- remove_page page
27
-
28
- page.update_pg_search_document
29
- page.all_elements.includes(:ingredients).find_each do |element|
30
- element.ingredients.select { |i| Alchemy::PgSearch.is_searchable?(i.type) }.each do |ingredient|
31
- ingredient.update_pg_search_document
32
- end
33
- end
34
- end
35
-
36
- ##
37
- # search for page results
38
- #
39
- # @param query [string]
40
- # @param ability [nil|CanCan::Ability]
41
- # @return [ActiveRecord::Relation]
42
- def self.search(query, ability: nil)
43
- query = ::PgSearch.multisearch(query)
44
- .select("JSON_AGG(content) as content", :page_id)
45
- .reorder("")
46
- .group(:page_id)
47
- .joins(:page)
48
-
49
- query = query.merge(Alchemy::Page.accessible_by(ability, :read)) if ability
50
-
51
- query
52
- end
53
- end
54
- end
55
- end