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 +4 -4
- data/.gitignore +1 -0
- data/CHANGELOG.md +19 -0
- data/README.md +17 -22
- data/app/extensions/alchemy/pg_search/page_extension.rb +2 -2
- data/app/extensions/alchemy/pg_search/pg_search_document_extension.rb +1 -15
- data/app/extensions/alchemy/{pg_search → search}/element_extension.rb +2 -2
- data/app/extensions/alchemy/search/ingredient_extension.rb +14 -0
- data/app/extensions/alchemy/search/page_extension.rb +10 -0
- data/app/helpers/alchemy/pg_search/search_helper.rb +2 -14
- data/app/jobs/alchemy/pg_search/index_page_job.rb +1 -1
- data/app/services/alchemy/search/search_page.rb +44 -0
- data/app/views/alchemy/search/_result.html.erb +2 -8
- data/app/views/alchemy/search/_results.html.erb +5 -5
- data/db/migrate/20241105092236_add_document_created_at_to_pg_search_documents.rb +6 -0
- data/lib/alchemy/pg_search/engine.rb +0 -4
- data/lib/alchemy/pg_search/version.rb +1 -1
- data/lib/alchemy-pg_search.rb +49 -14
- metadata +8 -11
- data/app/controller/alchemy/pg_search/controller_methods.rb +0 -96
- data/app/extensions/alchemy/pg_search/ingredient_extension.rb +0 -29
- data/db/migrate/20141211105526_add_searchable_to_alchemy_essence_texts.rb +0 -5
- data/db/migrate/20141211105942_add_searchable_to_alchemy_essence_richtexts.rb +0 -5
- data/db/migrate/20141211110126_add_searchable_to_alchemy_essence_pictures.rb +0 -5
- data/db/migrate/20210923081905_move_searchable_to_contents.rb +0 -45
- data/lib/alchemy/pg_search/search.rb +0 -55
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: '009bedc83da970afa4e20ea64375695711b50ef37027b4e4cbe08a7add274070'
|
4
|
+
data.tar.gz: c324b6915704b0ef189d539510d57ff80962be5f6244907bbfd9d82e0d13eec8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0eda59775b10f840a97362c244beb387e259e104d18f03e855984ea15437649d78c1a397a4a2f443e62c62bbd74f42288b77ce7ede6401d090ae28b1d3844d06
|
7
|
+
data.tar.gz: 6245d3b156a31fe0459fc9372de9c3ffc8ab7f123dd717402a7dc3aeb206876912069f88ae4d20a127039ee1754d45f87c6391633490f8363dde101a2cb8fb10
|
data/.gitignore
CHANGED
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
|
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
|
-
|
140
|
-
|
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
|
170
|
-
|
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
|
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
|
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::
|
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::
|
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
|
-
|
33
|
-
|
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)
|
@@ -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.
|
2
|
+
<% page = result.searchable %>
|
3
3
|
<h3><%= link_to page.name, show_alchemy_page_path(page) %></h3>
|
4
|
-
|
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
|
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:
|
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:
|
13
|
+
<%= render(partial: 'alchemy/search/result', collection: search_results) %>
|
14
14
|
</ul>
|
15
15
|
<% end %>
|
16
16
|
</div>
|
17
17
|
|
18
|
-
<% if
|
19
|
-
<%= paginate
|
18
|
+
<% if search_results.try(:total_pages) %>
|
19
|
+
<%= paginate search_results %>
|
20
20
|
<% 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
|
data/lib/alchemy-pg_search.rb
CHANGED
@@ -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
|
-
|
7
|
-
|
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
|
-
#
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
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
|
-
|
27
|
-
end
|
60
|
+
query = ::PgSearch.multisearch(query).includes(:searchable)
|
28
61
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
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:
|
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-
|
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.
|
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,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
|