alchemy-pg_search 5.2.0 → 6.0.0

Sign up to get free protection for your applications and to get access to all the features.
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