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 +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
|