alchemy-pg_search 3.0.0 → 4.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +5 -0
- data/CHANGELOG.md +26 -0
- data/Gemfile +3 -1
- data/README.md +80 -11
- data/Rakefile +29 -18
- data/alchemy-pg_search.gemspec +1 -1
- data/{lib → app/controller}/alchemy/pg_search/controller_methods.rb +11 -18
- data/{lib → app/extensions}/alchemy/pg_search/content_extension.rb +2 -11
- data/app/extensions/alchemy/pg_search/element_extension.rb +8 -0
- data/app/extensions/alchemy/pg_search/essence_picture_extension.rb +19 -0
- data/app/extensions/alchemy/pg_search/essence_richtext_extension.rb +20 -0
- data/app/extensions/alchemy/pg_search/essence_text_extension.rb +19 -0
- data/app/extensions/alchemy/pg_search/ingredient_extension.rb +20 -0
- data/app/extensions/alchemy/pg_search/page_extension.rb +30 -0
- data/app/extensions/alchemy/pg_search/pg_search_document_extension.rb +21 -0
- data/app/jobs/alchemy/pg_search/index_page_job.rb +10 -0
- data/app/views/alchemy/search/_result.html.erb +4 -5
- data/app/views/alchemy/search/_results.html.erb +2 -8
- data/db/migrate/20220826125413_add_page_id_column_to_pg_search_documents.rb +5 -0
- data/lib/alchemy/pg_search/engine.rb +14 -4
- data/lib/alchemy/pg_search/search.rb +59 -0
- data/lib/alchemy/pg_search/version.rb +1 -1
- data/lib/alchemy-pg_search.rb +28 -9
- data/lib/generators/alchemy/pg_search/install/install_generator.rb +25 -0
- metadata +19 -13
- data/lib/alchemy/pg_search/element_extension.rb +0 -53
- data/lib/alchemy/pg_search/page_extension.rb +0 -54
- data/lib/alchemy/pg_search/page_search_scope.rb +0 -9
- data/lib/generators/alchemy/pg_search/upgrade/templates/migration.rb.tt +0 -31
- data/lib/generators/alchemy/pg_search/upgrade/upgrade_generator.rb +0 -20
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 366fce3505557ca67b0a956441bc78e27c2bac492a8a008359571b2e768f2b18
|
4
|
+
data.tar.gz: c30b0e00f6c8e6dbd32326fc569ee94aba2b2b9bf7acbfeb71ce0084c930f3f3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c43a3f9bc07488d00811cc028f0be916a92afe87836dfb6e5017d454fcd5da9cf361e220be56b063a9132ade82947491e909ea924c2b6762441ddfd065257c19
|
7
|
+
data.tar.gz: 2c991f3476b1b82d960255d2f6d4d4adfd4610d6e93caa59bf8235326e485f2d4d36f0bcd16b0517472038e47e76835791cf7699c125aa5c3fc4c0eed8d91a5a
|
data/.gitignore
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,26 @@
|
|
1
|
+
# Changelog
|
2
|
+
|
3
|
+
## [v4.1.0](https://github.com/AlchemyCMS/alchemy-pg_search/tree/v4.1.0) (2022-09-19)
|
4
|
+
|
5
|
+
[Full Changelog](https://github.com/AlchemyCMS/alchemy-pg_search/compare/v4.0.0...v4.1.0)
|
6
|
+
|
7
|
+
**Closed issues:**
|
8
|
+
|
9
|
+
- V4 issue [\#32](https://github.com/AlchemyCMS/alchemy-pg_search/issues/32)
|
10
|
+
|
11
|
+
**Merged pull requests:**
|
12
|
+
|
13
|
+
- Allow to exclude pages, elements and ingredients from search index [\#35](https://github.com/AlchemyCMS/alchemy-pg_search/pull/35) ([tvdeyen](https://github.com/tvdeyen))
|
14
|
+
- Add multisearch configuration [\#34](https://github.com/AlchemyCMS/alchemy-pg_search/pull/34) ([kulturbande](https://github.com/kulturbande))
|
15
|
+
- Remove old ferret upgrader [\#31](https://github.com/AlchemyCMS/alchemy-pg_search/pull/31) ([tvdeyen](https://github.com/tvdeyen))
|
16
|
+
|
17
|
+
## 4.0.0 (2022-08-30)
|
18
|
+
|
19
|
+
- Introduced new search module [#30](https://github.com/AlchemyCMS/alchemy-pg_search/pull/30) by [kulturbande](https://github.com/kulturbande)
|
20
|
+
- Moved all extensions to a separate folder [#30](https://github.com/AlchemyCMS/alchemy-pg_search/pull/30) by [kulturbande](https://github.com/kulturbande)
|
21
|
+
- Enable pg_search multisearch [#30](https://github.com/AlchemyCMS/alchemy-pg_search/pull/30) by [kulturbande](https://github.com/kulturbande)
|
22
|
+
- Update Dummy App to Alchemy 6 [#30](https://github.com/AlchemyCMS/alchemy-pg_search/pull/30) by [kulturbande](https://github.com/kulturbande)
|
23
|
+
|
1
24
|
## 3.0.0 (2022-07-01)
|
2
25
|
|
3
26
|
- Changed searchable option initialization [#29](https://github.com/AlchemyCMS/alchemy-pg_search/pull/29) by [kulturbande](https://github.com/kulturbande)
|
@@ -29,3 +52,6 @@
|
|
29
52
|
|
30
53
|
- Rename the #search method into #full_text_search [#14](https://github.com/AlchemyCMS/alchemy-pg_search/pull/14) by [tvdeyen](https://github.com/tvdeyen)
|
31
54
|
- Add a page search scope class [#13](https://github.com/AlchemyCMS/alchemy-pg_search/pull/13) by [tvdeyen](https://github.com/tvdeyen)
|
55
|
+
|
56
|
+
|
57
|
+
\* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)*
|
data/Gemfile
CHANGED
@@ -3,7 +3,7 @@ source "https://rubygems.org"
|
|
3
3
|
gemspec
|
4
4
|
|
5
5
|
gem "rails", "~> 6.0.0"
|
6
|
-
ENV.fetch("ALCHEMY_BRANCH", "
|
6
|
+
ENV.fetch("ALCHEMY_BRANCH", "6.0-stable").tap do |branch|
|
7
7
|
gem "alchemy_cms", github: "AlchemyCMS/alchemy_cms", branch: branch
|
8
8
|
end
|
9
9
|
gem "sassc-rails"
|
@@ -18,3 +18,5 @@ group :test do
|
|
18
18
|
gem "pry-byebug"
|
19
19
|
gem "launchy"
|
20
20
|
end
|
21
|
+
|
22
|
+
gem "github_changelog_generator", "~> 1.16"
|
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 6.0 and above.
|
6
6
|
|
7
7
|
## Installation
|
8
8
|
|
@@ -18,11 +18,10 @@ And then execute:
|
|
18
18
|
$ bundle install
|
19
19
|
```
|
20
20
|
|
21
|
-
|
21
|
+
Run install script:
|
22
22
|
|
23
23
|
```shell
|
24
|
-
$ bin/
|
25
|
-
$ bin/rake db:migrate
|
24
|
+
$ bin/rails g alchemy:pg_search:install
|
26
25
|
```
|
27
26
|
|
28
27
|
## Usage
|
@@ -31,16 +30,79 @@ Every `EssenceText`, `EssenceRichtext` and `EssencePicture` will be indexed unle
|
|
31
30
|
|
32
31
|
### Disable Indexing
|
33
32
|
|
34
|
-
|
33
|
+
#### Exclude whole pages from the search index
|
34
|
+
|
35
|
+
Pass `searchable: false` to your page layout definitions and Alchemy will not index that particular page.
|
36
|
+
|
37
|
+
```yaml
|
38
|
+
# page_layouts.yml
|
39
|
+
- name: secret_page
|
40
|
+
searchable: false
|
41
|
+
elements:
|
42
|
+
- secret_sauce
|
43
|
+
```
|
44
|
+
|
45
|
+
#### Exclude whole elements from the search index
|
46
|
+
|
47
|
+
Pass `searchable: false` to your element definitions and Alchemy will not index that particular element.
|
48
|
+
|
49
|
+
```yaml
|
50
|
+
# elements.yml
|
51
|
+
- name: secret_sauce
|
52
|
+
searchable: false
|
53
|
+
ingredients:
|
54
|
+
- name: sauce
|
55
|
+
type: Text
|
56
|
+
default: 'This is my secret sauce.'
|
57
|
+
```
|
58
|
+
|
59
|
+
#### Exclude single contents from being indexed
|
60
|
+
|
61
|
+
Pass `searchable: false` to your content definitions and Alchemy will not index that particular content.
|
35
62
|
|
36
63
|
```yaml
|
37
64
|
# elements.yml
|
38
65
|
- name: secrets
|
39
66
|
contents:
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
67
|
+
- name: passwords
|
68
|
+
type: EssenceText
|
69
|
+
searchable: false
|
70
|
+
default: 'This is my secret password.'
|
71
|
+
```
|
72
|
+
|
73
|
+
The same works for `ingredients` as well
|
74
|
+
|
75
|
+
```yaml
|
76
|
+
# elements.yml
|
77
|
+
- name: secrets
|
78
|
+
ingredients:
|
79
|
+
- name: passwords
|
80
|
+
type: Text
|
81
|
+
searchable: false
|
82
|
+
default: 'This is my secret password.'
|
83
|
+
```
|
84
|
+
|
85
|
+
### Configure Behavior
|
86
|
+
|
87
|
+
Configure the gem in an initializer. The default configurations are:
|
88
|
+
|
89
|
+
```ruby
|
90
|
+
Alchemy::PgSearch.config = {
|
91
|
+
paginate_per: 10, # amount of results per page
|
92
|
+
}
|
93
|
+
```
|
94
|
+
|
95
|
+
You can also overwrite the default multisearch configuration to use other search strategies. For more information take
|
96
|
+
a look into the [PgSearch Readme](https://github.com/Casecommons/pg_search#configuring-multi-search).
|
97
|
+
|
98
|
+
```ruby
|
99
|
+
Rails.application.config.after_initialize do
|
100
|
+
::PgSearch.multisearch_options = {
|
101
|
+
using: {
|
102
|
+
tsearch: { prefix: true }
|
103
|
+
}
|
104
|
+
}
|
105
|
+
end
|
44
106
|
```
|
45
107
|
|
46
108
|
### Rendering search results.
|
@@ -120,13 +182,20 @@ en:
|
|
120
182
|
|
121
183
|
## Upgrading
|
122
184
|
|
123
|
-
If you are upgrading from
|
185
|
+
If you are upgrading from v3.0.0 please run the install generator:
|
124
186
|
|
125
187
|
```shell
|
126
|
-
$ bin/rails g alchemy:pg_search:
|
188
|
+
$ bin/rails g alchemy:pg_search:install
|
127
189
|
$ bin/rake db:migrate
|
128
190
|
```
|
129
191
|
|
192
|
+
and reindex your database in your Rails console
|
193
|
+
|
194
|
+
```rb
|
195
|
+
# rails console
|
196
|
+
$ Alchemy::PgSearch::Search.rebuild
|
197
|
+
```
|
198
|
+
|
130
199
|
## Contributing
|
131
200
|
|
132
201
|
1. Fork it
|
data/Rakefile
CHANGED
@@ -1,46 +1,57 @@
|
|
1
1
|
#!/usr/bin/env rake
|
2
2
|
begin
|
3
|
-
require
|
3
|
+
require "bundler/setup"
|
4
4
|
rescue LoadError
|
5
|
-
puts
|
5
|
+
puts "You must `gem install bundler` and `bundle install` to run rake tasks"
|
6
6
|
end
|
7
7
|
|
8
8
|
begin
|
9
|
-
require
|
9
|
+
require "rdoc/task"
|
10
10
|
rescue LoadError
|
11
|
-
require
|
12
|
-
require
|
11
|
+
require "rdoc/rdoc"
|
12
|
+
require "rake/rdoctask"
|
13
13
|
RDoc::Task = Rake::RDocTask
|
14
14
|
end
|
15
15
|
|
16
|
-
desc
|
16
|
+
desc "Generate documentation for Alchemy CMS."
|
17
17
|
RDoc::Task.new(:rdoc) do |rdoc|
|
18
|
-
rdoc.rdoc_dir =
|
19
|
-
rdoc.title
|
20
|
-
rdoc.options <<
|
21
|
-
rdoc.rdoc_files.include(
|
22
|
-
rdoc.rdoc_files.include(
|
23
|
-
rdoc.rdoc_files.include(
|
18
|
+
rdoc.rdoc_dir = "rdoc"
|
19
|
+
rdoc.title = "Alchemy PgSearch"
|
20
|
+
rdoc.options << "--line-numbers" << "--inline-source"
|
21
|
+
rdoc.rdoc_files.include("README.md")
|
22
|
+
rdoc.rdoc_files.include("lib/**/*.rb")
|
23
|
+
rdoc.rdoc_files.include("app/**/*.rb")
|
24
24
|
end
|
25
25
|
|
26
26
|
APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__)
|
27
|
-
load
|
27
|
+
load "rails/tasks/engine.rake"
|
28
28
|
|
29
|
-
require
|
30
|
-
require
|
29
|
+
require "rspec/core"
|
30
|
+
require "rspec/core/rake_task"
|
31
31
|
|
32
32
|
RSpec::Core::RakeTask.new(:spec)
|
33
33
|
|
34
|
-
task :default => [
|
34
|
+
task :default => ["alchemy:spec:prepare", :spec]
|
35
35
|
|
36
36
|
Bundler::GemHelper.install_tasks
|
37
37
|
|
38
38
|
namespace :alchemy do
|
39
39
|
namespace :spec do
|
40
|
-
|
41
40
|
desc "Prepares database for testing"
|
42
41
|
task :prepare do
|
43
|
-
system
|
42
|
+
system "cd spec/dummy; RAILS_ENV=test bin/rake db:setup db:seed; cd -"
|
44
43
|
end
|
45
44
|
end
|
46
45
|
end
|
46
|
+
|
47
|
+
require "github_changelog_generator/task"
|
48
|
+
require "alchemy/pg_search/version"
|
49
|
+
|
50
|
+
namespace :changelog do
|
51
|
+
GitHubChangelogGenerator::RakeTask.new :update do |config|
|
52
|
+
config.user = "AlchemyCMS"
|
53
|
+
config.project = "alchemy-pg_search"
|
54
|
+
config.since_tag = "v4.0.0"
|
55
|
+
config.future_release = "v#{Alchemy::PgSearch::VERSION}"
|
56
|
+
end
|
57
|
+
end
|
data/alchemy-pg_search.gemspec
CHANGED
@@ -16,7 +16,7 @@ Gem::Specification.new do |spec|
|
|
16
16
|
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^spec/}) }
|
17
17
|
spec.require_paths = ["lib"]
|
18
18
|
|
19
|
-
spec.add_runtime_dependency "alchemy_cms", [">=
|
19
|
+
spec.add_runtime_dependency "alchemy_cms", [">= 6.0", "< 7"]
|
20
20
|
spec.add_runtime_dependency "pg_search", ["~> 2.1"]
|
21
21
|
spec.add_runtime_dependency "pg"
|
22
22
|
|
@@ -35,7 +35,7 @@ module Alchemy
|
|
35
35
|
#
|
36
36
|
def perform_search
|
37
37
|
if self.class == Alchemy::Admin::PagesController && params[:query].blank?
|
38
|
-
params[:query] =
|
38
|
+
params[:query] = "lorem"
|
39
39
|
end
|
40
40
|
return if params[:query].blank?
|
41
41
|
@search_results = search_results
|
@@ -49,31 +49,24 @@ module Alchemy
|
|
49
49
|
# @return [Array]
|
50
50
|
#
|
51
51
|
def search_results
|
52
|
-
|
53
|
-
# Since CanCan cannot (oh the irony) merge +accessible_by+ scope with pg_search scopes,
|
54
|
-
# we need to fake a page object here
|
55
|
-
if can? :show, Alchemy::Page.new(restricted: true, public_on: Date.current)
|
56
|
-
pages.full_text_search(params[:query])
|
57
|
-
else
|
58
|
-
pages.not_restricted.full_text_search(params[:query])
|
59
|
-
end
|
52
|
+
Alchemy::PgSearch.search params[:query], ability: current_ability
|
60
53
|
end
|
61
54
|
|
62
55
|
# A view helper that loads the search result page.
|
63
56
|
#
|
64
|
-
# @return [Alchemy::Page]
|
57
|
+
# @return [Alchemy::Page,nil]
|
65
58
|
#
|
66
59
|
def search_result_page
|
67
60
|
@search_result_page ||= begin
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
61
|
+
page = Page.published.find_by(
|
62
|
+
page_layout: search_result_page_layout["name"],
|
63
|
+
language_id: Language.current.id,
|
64
|
+
)
|
65
|
+
if page.nil?
|
66
|
+
logger.warn "\n++++++\nNo published search result page found. Please create one or publish your search result page.\n++++++\n"
|
67
|
+
end
|
68
|
+
page
|
74
69
|
end
|
75
|
-
page
|
76
|
-
end
|
77
70
|
end
|
78
71
|
|
79
72
|
def search_result_page_layout
|
@@ -10,17 +10,8 @@ module Alchemy::PgSearch::ContentExtension
|
|
10
10
|
end
|
11
11
|
|
12
12
|
module InstanceMethods
|
13
|
-
def
|
14
|
-
|
15
|
-
when "Alchemy::EssencePicture"
|
16
|
-
essence.caption
|
17
|
-
when "Alchemy::EssenceRichtext"
|
18
|
-
essence.stripped_body
|
19
|
-
when "Alchemy::EssenceText"
|
20
|
-
essence.body
|
21
|
-
else
|
22
|
-
ingredient
|
23
|
-
end
|
13
|
+
def searchable?
|
14
|
+
searchable && element.searchable?
|
24
15
|
end
|
25
16
|
|
26
17
|
Alchemy::Content.prepend self
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Alchemy::PgSearch::EssencePictureExtension
|
2
|
+
|
3
|
+
def self.prepended(base)
|
4
|
+
base.include PgSearch::Model
|
5
|
+
base.multisearchable(
|
6
|
+
against: [
|
7
|
+
:caption
|
8
|
+
],
|
9
|
+
additional_attributes: -> (essence_picture) { { page_id: essence_picture.page.id } },
|
10
|
+
if: :searchable?
|
11
|
+
)
|
12
|
+
end
|
13
|
+
|
14
|
+
def searchable?
|
15
|
+
caption.present? && !!content&.searchable?
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
Alchemy::EssencePicture.prepend(Alchemy::PgSearch::EssencePictureExtension)
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Alchemy::PgSearch::EssenceRichtextExtension
|
2
|
+
|
3
|
+
def self.prepended(base)
|
4
|
+
base.include PgSearch::Model
|
5
|
+
base.multisearchable(
|
6
|
+
against: [
|
7
|
+
:stripped_body
|
8
|
+
],
|
9
|
+
additional_attributes: -> (essence_richtext) { { page_id: essence_richtext.page.id } },
|
10
|
+
if: :searchable?
|
11
|
+
)
|
12
|
+
end
|
13
|
+
|
14
|
+
def searchable?
|
15
|
+
stripped_body.present? && !!content&.searchable?
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
Alchemy::EssenceRichtext.prepend(Alchemy::PgSearch::EssenceRichtextExtension)
|
20
|
+
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Alchemy::PgSearch::EssenceTextExtension
|
2
|
+
def self.prepended(base)
|
3
|
+
base.include PgSearch::Model
|
4
|
+
base.multisearchable(
|
5
|
+
against: [
|
6
|
+
:body
|
7
|
+
],
|
8
|
+
additional_attributes: -> (essence_text) { { page_id: essence_text.page.id } },
|
9
|
+
if: :searchable?
|
10
|
+
)
|
11
|
+
end
|
12
|
+
|
13
|
+
def searchable?
|
14
|
+
body.present? && !!content&.searchable?
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
Alchemy::EssenceText.prepend(Alchemy::PgSearch::EssenceTextExtension)
|
19
|
+
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Alchemy::PgSearch::IngredientExtension
|
2
|
+
def self.prepended(base)
|
3
|
+
base.include PgSearch::Model
|
4
|
+
base.multisearchable(
|
5
|
+
against: [
|
6
|
+
:value,
|
7
|
+
],
|
8
|
+
additional_attributes: ->(ingredient) { { page_id: ingredient.element.page.id } },
|
9
|
+
if: :searchable?,
|
10
|
+
)
|
11
|
+
end
|
12
|
+
|
13
|
+
def searchable?
|
14
|
+
Alchemy::PgSearch.is_searchable?(type) &&
|
15
|
+
(definition.key?(:searchable) ? definition[:searchable] : true) &&
|
16
|
+
value.present? && !!element&.searchable?
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
Alchemy::Ingredient.prepend(Alchemy::PgSearch::IngredientExtension)
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# Enable Postgresql full text indexing.
|
2
|
+
#
|
3
|
+
module Alchemy::PgSearch::PageExtension
|
4
|
+
def self.prepended(base)
|
5
|
+
base.include PgSearch::Model
|
6
|
+
base.after_save :remove_unpublished_page
|
7
|
+
base.multisearchable(
|
8
|
+
against: [
|
9
|
+
:meta_description,
|
10
|
+
:meta_keywords,
|
11
|
+
:name,
|
12
|
+
],
|
13
|
+
additional_attributes: ->(page) { { page_id: page.id } },
|
14
|
+
if: :searchable?,
|
15
|
+
)
|
16
|
+
end
|
17
|
+
|
18
|
+
def searchable?
|
19
|
+
(definition.key?(:searchable) ? definition[:searchable] : true) &&
|
20
|
+
public? && !layoutpage?
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def remove_unpublished_page
|
26
|
+
Alchemy::PgSearch::Search.remove_page(self) unless searchable?
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
Alchemy::Page.prepend(Alchemy::PgSearch::PageExtension)
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Alchemy::PgSearch::PgSearchDocumentExtension
|
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
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
PgSearch::Document.prepend(Alchemy::PgSearch::PgSearchDocumentExtension)
|
@@ -1,10 +1,9 @@
|
|
1
1
|
<li class="search_result">
|
2
|
+
<% page = result.page %>
|
2
3
|
<h3><%= link_to page.name, show_alchemy_page_path(page) %></h3>
|
3
|
-
<% if
|
4
|
-
<%
|
5
|
-
|
6
|
-
<p><%= highlighted_excerpt(content.searchable_ingredient.to_s, params[:query]) %></p>
|
7
|
-
<% end %>
|
4
|
+
<% if result.excerpts.any? %>
|
5
|
+
<% result.excerpts.each do |excerpt| %>
|
6
|
+
<p><%= highlighted_excerpt(excerpt, params[:query]) %></p>
|
8
7
|
<% end %>
|
9
8
|
<% else %>
|
10
9
|
<p><%= page.meta_description %></p>
|
@@ -7,16 +7,10 @@
|
|
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
|
-
|
10
|
+
count: @search_results.try(:total_count) || @search_results.size) %>
|
11
11
|
</h2>
|
12
12
|
<ul class="search_result_list">
|
13
|
-
|
14
|
-
<%= render('alchemy/search/result',
|
15
|
-
elements: page.element_search_results(params[:query]),
|
16
|
-
options: options,
|
17
|
-
page: page
|
18
|
-
) %>
|
19
|
-
<% end %>
|
13
|
+
<%= render(partial: 'alchemy/search/result', collection: @search_results) %>
|
20
14
|
</ul>
|
21
15
|
<% end %>
|
22
16
|
</div>
|
@@ -7,13 +7,23 @@ module Alchemy
|
|
7
7
|
engine_name "alchemy_pg_search"
|
8
8
|
|
9
9
|
config.to_prepare do
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
require_relative "./page_extension"
|
10
|
+
Dir.glob(Alchemy::PgSearch::Engine.root.join("app", "extensions", "**", "*_extension.rb")) do |c|
|
11
|
+
require_dependency(c)
|
12
|
+
end
|
14
13
|
|
15
14
|
# We need to have the search methods present in all Alchemy controllers
|
16
15
|
Alchemy::BaseController.send(:include, Alchemy::PgSearch::ControllerMethods)
|
16
|
+
|
17
|
+
# reindex the page after it was published
|
18
|
+
Alchemy.publish_targets << Alchemy::PgSearch::IndexPageJob
|
19
|
+
|
20
|
+
# configure multiselect to find also partial words
|
21
|
+
# @link https://github.com/Casecommons/pg_search#searching-using-different-search-features
|
22
|
+
::PgSearch.multisearch_options = {
|
23
|
+
using: {
|
24
|
+
tsearch: { prefix: true }
|
25
|
+
}
|
26
|
+
}
|
17
27
|
end
|
18
28
|
end
|
19
29
|
end
|
@@ -0,0 +1,59 @@
|
|
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] + Alchemy::PgSearch.searchable_essence_classes).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 essences
|
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, contents: :essence).find_each do |element|
|
30
|
+
element.contents.each do |content|
|
31
|
+
content.essence.update_pg_search_document if Alchemy::PgSearch.is_searchable?(content.essence_type)
|
32
|
+
end
|
33
|
+
|
34
|
+
element.ingredients.each do |ingredient|
|
35
|
+
ingredient.update_pg_search_document if Alchemy::PgSearch.is_searchable?(ingredient.type)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
##
|
41
|
+
# search for page results
|
42
|
+
#
|
43
|
+
# @param query [string]
|
44
|
+
# @param ability [nil|CanCan::Ability]
|
45
|
+
# @return [ActiveRecord::Relation]
|
46
|
+
def self.search(query, ability: nil)
|
47
|
+
query = ::PgSearch.multisearch(query)
|
48
|
+
.select("JSON_AGG(content) as content", :page_id)
|
49
|
+
.reorder("")
|
50
|
+
.group(:page_id)
|
51
|
+
.joins(:page)
|
52
|
+
|
53
|
+
query = query.merge(Alchemy::Page.accessible_by(ability, :read)) if ability
|
54
|
+
|
55
|
+
query
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
data/lib/alchemy-pg_search.rb
CHANGED
@@ -1,23 +1,42 @@
|
|
1
1
|
require "alchemy/pg_search/engine"
|
2
2
|
require "alchemy/pg_search/config"
|
3
|
-
require "alchemy/pg_search/
|
3
|
+
require "alchemy/pg_search/search"
|
4
4
|
|
5
5
|
module Alchemy
|
6
6
|
module PgSearch
|
7
|
-
|
8
|
-
DEFAULT_CONFIG = {
|
9
|
-
page_search_scope: PageSearchScope.new,
|
10
|
-
}
|
7
|
+
SEARCHABLE_INGREDIENTS = %w[Text Richtext Picture]
|
11
8
|
|
12
9
|
extend Config
|
13
|
-
self.config = DEFAULT_CONFIG
|
14
10
|
|
15
|
-
|
16
|
-
|
11
|
+
##
|
12
|
+
# is essence or ingredient searchable?
|
13
|
+
# @param essence_type [string]
|
14
|
+
# @return [boolean]
|
15
|
+
def self.is_searchable?(essence_type)
|
16
|
+
SEARCHABLE_INGREDIENTS.include?(essence_type.gsub(/Alchemy::(Essence|Ingredients::)/, ""))
|
17
17
|
end
|
18
18
|
|
19
|
+
##
|
20
|
+
# generate an array of all supported essences classes
|
21
|
+
# @return [array]
|
19
22
|
def self.searchable_essence_classes
|
20
|
-
|
23
|
+
SEARCHABLE_INGREDIENTS.map { |k| "Alchemy::Essence#{k.classify}".constantize }
|
24
|
+
end
|
25
|
+
|
26
|
+
##
|
27
|
+
# search for page results
|
28
|
+
#
|
29
|
+
# @param query [string]
|
30
|
+
# @param ability [nil|CanCan::Ability]
|
31
|
+
# @return [ActiveRecord::Relation]
|
32
|
+
def self.search(query, ability: nil)
|
33
|
+
Search.search(query, ability: ability)
|
34
|
+
end
|
35
|
+
|
36
|
+
##
|
37
|
+
# index all supported Alchemy models
|
38
|
+
def self.rebuild
|
39
|
+
Search.rebuild
|
21
40
|
end
|
22
41
|
end
|
23
42
|
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require "rails/generators"
|
2
|
+
require "rails/generators/active_record/migration"
|
3
|
+
|
4
|
+
module Alchemy
|
5
|
+
module PgSearch
|
6
|
+
class InstallGenerator < ::Rails::Generators::Base
|
7
|
+
include ActiveRecord::Generators::Migration
|
8
|
+
|
9
|
+
desc "Install Alchemy PgSearch - Gem into Rails App."
|
10
|
+
|
11
|
+
source_root(File.expand_path("../../../..", __dir__))
|
12
|
+
|
13
|
+
def install_migrations
|
14
|
+
# Install pg_search multisearch - migration - the pg_search is not testing if the migration already exists
|
15
|
+
generate("pg_search:migration:multisearch", abort_on_failure: true) unless self.class.migration_exists?("db/migrate", 'create_pg_search_documents')
|
16
|
+
|
17
|
+
# Copy the migrations of the gem
|
18
|
+
rake("alchemy_pg_search:install:migrations", abort_on_failure: true)
|
19
|
+
|
20
|
+
# run migrations
|
21
|
+
rake("db:migrate", abort_on_failure: true)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
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: 4.1.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: 2022-
|
11
|
+
date: 2022-09-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: alchemy_cms
|
@@ -16,20 +16,20 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
19
|
+
version: '6.0'
|
20
20
|
- - "<"
|
21
21
|
- !ruby/object:Gem::Version
|
22
|
-
version: '
|
22
|
+
version: '7'
|
23
23
|
type: :runtime
|
24
24
|
prerelease: false
|
25
25
|
version_requirements: !ruby/object:Gem::Requirement
|
26
26
|
requirements:
|
27
27
|
- - ">="
|
28
28
|
- !ruby/object:Gem::Version
|
29
|
-
version: '
|
29
|
+
version: '6.0'
|
30
30
|
- - "<"
|
31
31
|
- !ruby/object:Gem::Version
|
32
|
-
version: '
|
32
|
+
version: '7'
|
33
33
|
- !ruby/object:Gem::Dependency
|
34
34
|
name: pg_search
|
35
35
|
requirement: !ruby/object:Gem::Requirement
|
@@ -115,7 +115,17 @@ 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/content_extension.rb
|
120
|
+
- app/extensions/alchemy/pg_search/element_extension.rb
|
121
|
+
- app/extensions/alchemy/pg_search/essence_picture_extension.rb
|
122
|
+
- app/extensions/alchemy/pg_search/essence_richtext_extension.rb
|
123
|
+
- app/extensions/alchemy/pg_search/essence_text_extension.rb
|
124
|
+
- app/extensions/alchemy/pg_search/ingredient_extension.rb
|
125
|
+
- app/extensions/alchemy/pg_search/page_extension.rb
|
126
|
+
- app/extensions/alchemy/pg_search/pg_search_document_extension.rb
|
118
127
|
- app/helpers/alchemy/pg_search/search_helper.rb
|
128
|
+
- app/jobs/alchemy/pg_search/index_page_job.rb
|
119
129
|
- app/views/alchemy/search/_form.html.erb
|
120
130
|
- app/views/alchemy/search/_result.html.erb
|
121
131
|
- app/views/alchemy/search/_results.html.erb
|
@@ -127,17 +137,13 @@ files:
|
|
127
137
|
- db/migrate/20141211105942_add_searchable_to_alchemy_essence_richtexts.rb
|
128
138
|
- db/migrate/20141211110126_add_searchable_to_alchemy_essence_pictures.rb
|
129
139
|
- db/migrate/20210923081905_move_searchable_to_contents.rb
|
140
|
+
- db/migrate/20220826125413_add_page_id_column_to_pg_search_documents.rb
|
130
141
|
- lib/alchemy-pg_search.rb
|
131
142
|
- lib/alchemy/pg_search/config.rb
|
132
|
-
- lib/alchemy/pg_search/content_extension.rb
|
133
|
-
- lib/alchemy/pg_search/controller_methods.rb
|
134
|
-
- lib/alchemy/pg_search/element_extension.rb
|
135
143
|
- lib/alchemy/pg_search/engine.rb
|
136
|
-
- lib/alchemy/pg_search/
|
137
|
-
- lib/alchemy/pg_search/page_search_scope.rb
|
144
|
+
- lib/alchemy/pg_search/search.rb
|
138
145
|
- lib/alchemy/pg_search/version.rb
|
139
|
-
- lib/generators/alchemy/pg_search/
|
140
|
-
- lib/generators/alchemy/pg_search/upgrade/upgrade_generator.rb
|
146
|
+
- lib/generators/alchemy/pg_search/install/install_generator.rb
|
141
147
|
- lib/generators/alchemy/pg_search/views/views_generator.rb
|
142
148
|
- lib/tasks/alchemy/pg_search_tasks.rake
|
143
149
|
homepage: https://alchemy-cms.com
|
@@ -1,53 +0,0 @@
|
|
1
|
-
Alchemy::Element.class_eval do
|
2
|
-
include PgSearch::Model
|
3
|
-
|
4
|
-
pg_search_scope :full_text_search,
|
5
|
-
associated_against: {
|
6
|
-
searchable_essence_texts: :body,
|
7
|
-
searchable_essence_richtexts: :stripped_body,
|
8
|
-
searchable_essence_pictures: :caption,
|
9
|
-
},
|
10
|
-
using: {
|
11
|
-
tsearch: { prefix: true },
|
12
|
-
}
|
13
|
-
|
14
|
-
has_many :searchable_essence_texts,
|
15
|
-
-> {
|
16
|
-
includes(:element)
|
17
|
-
.where(alchemy_contents: { searchable: true })
|
18
|
-
.where(alchemy_elements: { public: true })
|
19
|
-
},
|
20
|
-
class_name: "Alchemy::EssenceText",
|
21
|
-
source_type: "Alchemy::EssenceText",
|
22
|
-
through: :contents,
|
23
|
-
source: :essence
|
24
|
-
|
25
|
-
has_many :searchable_essence_richtexts,
|
26
|
-
-> {
|
27
|
-
includes(:element)
|
28
|
-
.where(alchemy_contents: { searchable: true })
|
29
|
-
.where(alchemy_elements: { public: true })
|
30
|
-
},
|
31
|
-
class_name: "Alchemy::EssenceRichtext",
|
32
|
-
source_type: "Alchemy::EssenceRichtext",
|
33
|
-
through: :contents,
|
34
|
-
source: :essence
|
35
|
-
|
36
|
-
has_many :searchable_essence_pictures,
|
37
|
-
-> {
|
38
|
-
includes(:element)
|
39
|
-
.where(alchemy_contents: { searchable: true })
|
40
|
-
.where(alchemy_elements: { public: true })
|
41
|
-
},
|
42
|
-
class_name: "Alchemy::EssencePicture",
|
43
|
-
source_type: "Alchemy::EssencePicture",
|
44
|
-
through: :contents,
|
45
|
-
source: :essence
|
46
|
-
|
47
|
-
has_many :searchable_contents,
|
48
|
-
-> {
|
49
|
-
where(essence_type: Alchemy::PgSearch::SEARCHABLE_ESSENCES.map { |k| "Alchemy::#{k}" })
|
50
|
-
},
|
51
|
-
class_name: "Alchemy::Content",
|
52
|
-
source: :contents
|
53
|
-
end
|
@@ -1,54 +0,0 @@
|
|
1
|
-
# Enable Postgresql full text indexing.
|
2
|
-
#
|
3
|
-
module Alchemy::PgSearch::PageExtension
|
4
|
-
def self.extended(base)
|
5
|
-
base.include InstanceMethods
|
6
|
-
base.include PgSearch::Model
|
7
|
-
|
8
|
-
base.pg_search_scope(
|
9
|
-
:full_text_search,
|
10
|
-
against: {
|
11
|
-
meta_description: "B",
|
12
|
-
meta_keywords: "B",
|
13
|
-
title: "B",
|
14
|
-
name: "A",
|
15
|
-
},
|
16
|
-
associated_against: {
|
17
|
-
searchable_essence_texts: :body,
|
18
|
-
searchable_essence_richtexts: :stripped_body,
|
19
|
-
searchable_essence_pictures: :caption,
|
20
|
-
},
|
21
|
-
using: {
|
22
|
-
tsearch: { prefix: true },
|
23
|
-
},
|
24
|
-
)
|
25
|
-
|
26
|
-
base.has_many(
|
27
|
-
:searchable_contents,
|
28
|
-
-> {
|
29
|
-
where(alchemy_elements: { public: true })
|
30
|
-
.where(searchable: true)
|
31
|
-
},
|
32
|
-
class_name: "Alchemy::Content",
|
33
|
-
through: :all_elements,
|
34
|
-
)
|
35
|
-
|
36
|
-
Alchemy::PgSearch::SEARCHABLE_ESSENCES.each do |klass|
|
37
|
-
base.has_many(
|
38
|
-
:"searchable_#{klass.underscore.pluralize}",
|
39
|
-
class_name: "Alchemy::#{klass}",
|
40
|
-
source_type: "Alchemy::#{klass}",
|
41
|
-
through: :searchable_contents,
|
42
|
-
source: :essence,
|
43
|
-
)
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
module InstanceMethods
|
48
|
-
def element_search_results(query)
|
49
|
-
all_elements.full_text_search(query)
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
Alchemy::Page.extend self
|
54
|
-
end
|
@@ -1,31 +0,0 @@
|
|
1
|
-
require 'yaml'
|
2
|
-
|
3
|
-
class UpgradeFromAlchemyFerret < ActiveRecord::Migration
|
4
|
-
def up
|
5
|
-
not_searchable_elements.each do |element|
|
6
|
-
element['contents'].each do |content|
|
7
|
-
next unless Alchemy::PgSearch.is_searchable_essence?(content)
|
8
|
-
next if content['searchable'].nil?
|
9
|
-
"Alchemy::#{content['type'].classify}".constantize
|
10
|
-
.joins(content: :element)
|
11
|
-
.where(alchemy_contents: {name: content['name']})
|
12
|
-
.where(alchemy_elements: {name: element['name']})
|
13
|
-
.update_all(searchable: false)
|
14
|
-
say "Sets searchable to false for `#{content['name']}` contents of `#{element['name']}` elements."
|
15
|
-
end
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
private
|
20
|
-
|
21
|
-
def elements_yml
|
22
|
-
@elements_yml ||= YAML.load_file Rails.root.join('config/alchemy/elements.yml')
|
23
|
-
end
|
24
|
-
|
25
|
-
def not_searchable_elements
|
26
|
-
elements_yml.select do |element|
|
27
|
-
next if !element['contents']
|
28
|
-
element['contents'].any? { |content| content['searchable'] == false }
|
29
|
-
end
|
30
|
-
end
|
31
|
-
end
|
@@ -1,20 +0,0 @@
|
|
1
|
-
require 'rails'
|
2
|
-
|
3
|
-
module Alchemy
|
4
|
-
module PgSearch
|
5
|
-
class UpgradeGenerator < ::Rails::Generators::Base
|
6
|
-
desc "This generator upgrades your project from alchemy-ferret based projects."
|
7
|
-
source_root File.expand_path('templates', File.dirname(__FILE__))
|
8
|
-
|
9
|
-
def replace_element_config
|
10
|
-
gsub_file Rails.root.join('config/alchemy/elements.yml'),
|
11
|
-
'do_not_index: true', 'searchable: false'
|
12
|
-
end
|
13
|
-
|
14
|
-
def copy_migration_file
|
15
|
-
timestamp = Time.now.strftime('%Y%m%d%H%M%S')
|
16
|
-
copy_file "migration.rb.tt", Rails.root.join("db/migrate/#{timestamp}_upgrade_from_alchemy_ferret.alchemy_pg_search.rb")
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|