alchemy-pg_search 3.0.0 → 4.1.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.
Files changed (31) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +5 -0
  3. data/CHANGELOG.md +26 -0
  4. data/Gemfile +3 -1
  5. data/README.md +80 -11
  6. data/Rakefile +29 -18
  7. data/alchemy-pg_search.gemspec +1 -1
  8. data/{lib → app/controller}/alchemy/pg_search/controller_methods.rb +11 -18
  9. data/{lib → app/extensions}/alchemy/pg_search/content_extension.rb +2 -11
  10. data/app/extensions/alchemy/pg_search/element_extension.rb +8 -0
  11. data/app/extensions/alchemy/pg_search/essence_picture_extension.rb +19 -0
  12. data/app/extensions/alchemy/pg_search/essence_richtext_extension.rb +20 -0
  13. data/app/extensions/alchemy/pg_search/essence_text_extension.rb +19 -0
  14. data/app/extensions/alchemy/pg_search/ingredient_extension.rb +20 -0
  15. data/app/extensions/alchemy/pg_search/page_extension.rb +30 -0
  16. data/app/extensions/alchemy/pg_search/pg_search_document_extension.rb +21 -0
  17. data/app/jobs/alchemy/pg_search/index_page_job.rb +10 -0
  18. data/app/views/alchemy/search/_result.html.erb +4 -5
  19. data/app/views/alchemy/search/_results.html.erb +2 -8
  20. data/db/migrate/20220826125413_add_page_id_column_to_pg_search_documents.rb +5 -0
  21. data/lib/alchemy/pg_search/engine.rb +14 -4
  22. data/lib/alchemy/pg_search/search.rb +59 -0
  23. data/lib/alchemy/pg_search/version.rb +1 -1
  24. data/lib/alchemy-pg_search.rb +28 -9
  25. data/lib/generators/alchemy/pg_search/install/install_generator.rb +25 -0
  26. metadata +19 -13
  27. data/lib/alchemy/pg_search/element_extension.rb +0 -53
  28. data/lib/alchemy/pg_search/page_extension.rb +0 -54
  29. data/lib/alchemy/pg_search/page_search_scope.rb +0 -9
  30. data/lib/generators/alchemy/pg_search/upgrade/templates/migration.rb.tt +0 -31
  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: '041410896e0f98a81e3ead2437c7a5b02cb434e37d72ab5736db7ef3dbbaa5c0'
4
- data.tar.gz: 8b93ed647898f4b0c5540d4674233565beb2dcb9fa41370f0d228de48a1fb852
3
+ metadata.gz: 366fce3505557ca67b0a956441bc78e27c2bac492a8a008359571b2e768f2b18
4
+ data.tar.gz: c30b0e00f6c8e6dbd32326fc569ee94aba2b2b9bf7acbfeb71ce0084c930f3f3
5
5
  SHA512:
6
- metadata.gz: 2ede6256f9123b9bee9d13656c243a1ee3f370f40e04210674dd7ca2450ac83a66b2c3edda878ea6a0dca3b2aa64d042e6bdc4e0d2b3a676708455db4050db08
7
- data.tar.gz: cecc52649b848c514ebd07fff4b4822d19233f8fb50def4ffcb99acf14d1ad2ba45a07fc0ec53d3385fa49f566d8250b80862f530ec40acfd9edc841b10f3c8f
6
+ metadata.gz: c43a3f9bc07488d00811cc028f0be916a92afe87836dfb6e5017d454fcd5da9cf361e220be56b063a9132ade82947491e909ea924c2b6762441ddfd065257c19
7
+ data.tar.gz: 2c991f3476b1b82d960255d2f6d4d4adfd4610d6e93caa59bf8235326e485f2d4d36f0bcd16b0517472038e47e76835791cf7699c125aa5c3fc4c0eed8d91a5a
data/.gitignore CHANGED
@@ -7,3 +7,8 @@ spec/dummy/log/*.log
7
7
  spec/dummy/db/*.sqlite3*
8
8
  spec/dummy/tmp/*
9
9
  .ruby-*
10
+ node_modules
11
+ yarn.lock
12
+
13
+ .idea
14
+ .vscode
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", "5.3-stable").tap do |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 4.0 and above.
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
- Add migrations:
21
+ Run install script:
22
22
 
23
23
  ```shell
24
- $ bin/rake alchemy_pg_search:install:migrations
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
- Simply pass `searchable: false` to your content definitions and Alchemy will not index results from that particular content.
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
- - name: passwords
41
- type: EssenceText
42
- searchable: false
43
- default: 'This is my secret password.'
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 an old Alchemy < 4.0 based project that uses the ferret based full text search, please run this handy generator:
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:upgrade
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 'bundler/setup'
3
+ require "bundler/setup"
4
4
  rescue LoadError
5
- puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ puts "You must `gem install bundler` and `bundle install` to run rake tasks"
6
6
  end
7
7
 
8
8
  begin
9
- require 'rdoc/task'
9
+ require "rdoc/task"
10
10
  rescue LoadError
11
- require 'rdoc/rdoc'
12
- require 'rake/rdoctask'
11
+ require "rdoc/rdoc"
12
+ require "rake/rdoctask"
13
13
  RDoc::Task = Rake::RDocTask
14
14
  end
15
15
 
16
- desc 'Generate documentation for Alchemy CMS.'
16
+ desc "Generate documentation for Alchemy CMS."
17
17
  RDoc::Task.new(:rdoc) do |rdoc|
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')
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 'rails/tasks/engine.rake'
27
+ load "rails/tasks/engine.rake"
28
28
 
29
- require 'rspec/core'
30
- require 'rspec/core/rake_task'
29
+ require "rspec/core"
30
+ require "rspec/core/rake_task"
31
31
 
32
32
  RSpec::Core::RakeTask.new(:spec)
33
33
 
34
- task :default => ['alchemy:spec:prepare', :spec]
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 'cd spec/dummy; RAILS_ENV=test bin/rake db:setup db:seed; cd -'
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
@@ -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", [">= 5.0", "< 6"]
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] = 'lorem'
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
- pages = Alchemy::PgSearch.config[:page_search_scope].pages
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
- page = Page.published.find_by(
69
- page_layout: search_result_page_layout['name'],
70
- language_id: Language.current.id
71
- )
72
- if page.nil?
73
- logger.warn "\n++++++\nNo published search result page found. Please create one or publish your search result page.\n++++++\n"
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 searchable_ingredient
14
- case essence_type
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,8 @@
1
+ module Alchemy::PgSearch::ElementExtension
2
+ def searchable?
3
+ (definition.key?(:searchable) ? definition[:searchable] : true) &&
4
+ public? && page.searchable? && page_version.public?
5
+ end
6
+ end
7
+
8
+ Alchemy::Element.prepend(Alchemy::PgSearch::ElementExtension)
@@ -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)
@@ -0,0 +1,10 @@
1
+ module Alchemy
2
+ module PgSearch
3
+ class IndexPageJob < BaseJob
4
+ def perform(page)
5
+ Search.index_page(page)
6
+ end
7
+ end
8
+ end
9
+ end
10
+
@@ -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 elements.any? %>
4
- <% elements.each do |element| %>
5
- <% element.searchable_contents.each do |content| %>
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
- 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
- <% @search_results.each do |page| %>
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>
@@ -0,0 +1,5 @@
1
+ class AddPageIdColumnToPgSearchDocuments < ActiveRecord::Migration[6.0]
2
+ def change
3
+ add_reference :pg_search_documents, :page, index: true
4
+ end
5
+ end
@@ -7,13 +7,23 @@ module Alchemy
7
7
  engine_name "alchemy_pg_search"
8
8
 
9
9
  config.to_prepare do
10
- require_relative "./content_extension"
11
- require_relative "./controller_methods"
12
- require_relative "./element_extension"
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
@@ -1,5 +1,5 @@
1
1
  module Alchemy
2
2
  module PgSearch
3
- VERSION = "3.0.0"
3
+ VERSION = "4.1.0"
4
4
  end
5
5
  end
@@ -1,23 +1,42 @@
1
1
  require "alchemy/pg_search/engine"
2
2
  require "alchemy/pg_search/config"
3
- require "alchemy/pg_search/page_search_scope"
3
+ require "alchemy/pg_search/search"
4
4
 
5
5
  module Alchemy
6
6
  module PgSearch
7
- SEARCHABLE_ESSENCES = %w[EssenceText EssenceRichtext EssencePicture]
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
- def self.is_searchable_essence?(essence_type)
16
- SEARCHABLE_ESSENCES.include?(essence_type)
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
- SEARCHABLE_ESSENCES.map { |k| "Alchemy::#{k.classify}".constantize }
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: 3.0.0
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-07-01 00:00:00.000000000 Z
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: '5.0'
19
+ version: '6.0'
20
20
  - - "<"
21
21
  - !ruby/object:Gem::Version
22
- version: '6'
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: '5.0'
29
+ version: '6.0'
30
30
  - - "<"
31
31
  - !ruby/object:Gem::Version
32
- version: '6'
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/page_extension.rb
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/upgrade/templates/migration.rb.tt
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,9 +0,0 @@
1
- module Alchemy
2
- module PgSearch
3
- class PageSearchScope
4
- def pages
5
- Alchemy::Page.published.contentpages.with_language(Alchemy::Language.current.id)
6
- end
7
- end
8
- end
9
- 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