alchemy-pg_search 1.2.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 +7 -0
- data/.gitignore +9 -0
- data/.travis.yml +10 -0
- data/CHANGELOG.md +4 -0
- data/Gemfile +14 -0
- data/LICENSE +27 -0
- data/README.md +136 -0
- data/Rakefile +46 -0
- data/alchemy-pg_search.gemspec +26 -0
- data/app/helpers/alchemy/pg_search/search_helper.rb +53 -0
- data/app/views/alchemy/search/_form.html.erb +12 -0
- data/app/views/alchemy/search/_result.html.erb +13 -0
- data/app/views/alchemy/search/_results.html.erb +26 -0
- data/bin/rails +12 -0
- data/config/locales/alchemy.search.de.yml +14 -0
- data/config/locales/alchemy.search.en.yml +14 -0
- data/db/migrate/20141211105526_add_searchable_to_alchemy_essence_texts.rb +5 -0
- data/db/migrate/20141211105942_add_searchable_to_alchemy_essence_richtexts.rb +5 -0
- data/db/migrate/20141211110126_add_searchable_to_alchemy_essence_pictures.rb +5 -0
- data/lib/alchemy-pg_search.rb +23 -0
- data/lib/alchemy/pg_search/config.rb +17 -0
- data/lib/alchemy/pg_search/content_extension.rb +30 -0
- data/lib/alchemy/pg_search/controller_methods.rb +92 -0
- data/lib/alchemy/pg_search/element_extension.rb +40 -0
- data/lib/alchemy/pg_search/engine.rb +26 -0
- data/lib/alchemy/pg_search/page_extension.rb +45 -0
- data/lib/alchemy/pg_search/page_search_scope.rb +9 -0
- data/lib/alchemy/pg_search/searchable.rb +27 -0
- data/lib/alchemy/pg_search/version.rb +5 -0
- data/lib/generators/alchemy/pg_search/upgrade/templates/migration.rb.tt +31 -0
- data/lib/generators/alchemy/pg_search/upgrade/upgrade_generator.rb +20 -0
- data/lib/generators/alchemy/pg_search/views/views_generator.rb +15 -0
- data/lib/tasks/alchemy/pg_search_tasks.rake +4 -0
- metadata +166 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 49d88251b2d43c33f9512645a9d2a47f99370b91392dff0752bfa7827dab9484
|
4
|
+
data.tar.gz: 2b402676acaf93b70bb00c900f509bdc3fb2afedf8682040410ff4d0e75187ef
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 5ea033157ed66c0ac4f198d1879f9a78102a237f33977b9f43a0165e4e988baef011f1e28fc1e456e747c1ed134275526293da84f6d181019f668afeedf323e9
|
7
|
+
data.tar.gz: c7bb6894fad22d584ce463e72f57b261d3aa38de6abcd33e80eea39c2e8c55ebad30a279cb96acf9cb07a63a66a10dcbc52e6bb4e93675aeaad588a47a1960b8
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/CHANGELOG.md
ADDED
@@ -0,0 +1,4 @@
|
|
1
|
+
## 1.2.0 (2018-11-28)
|
2
|
+
|
3
|
+
- Rename the #search method into #full_text_search [#14](https://github.com/AlchemyCMS/alchemy-pg_search/pull/14) by [tvdeyen](https://github.com/tvdeyen)
|
4
|
+
- Add a page search scope class [#13](https://github.com/AlchemyCMS/alchemy-pg_search/pull/13) by [tvdeyen](https://github.com/tvdeyen)
|
data/Gemfile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
gemspec
|
4
|
+
|
5
|
+
gem 'alchemy_cms', github: 'AlchemyCMS/alchemy_cms', branch: '3.6-stable'
|
6
|
+
gem 'sassc-rails'
|
7
|
+
gem 'pg', '< 1.0'
|
8
|
+
|
9
|
+
group :test do
|
10
|
+
gem 'factory_girl_rails'
|
11
|
+
gem "capybara"
|
12
|
+
gem "pry"
|
13
|
+
gem 'launchy'
|
14
|
+
end
|
data/LICENSE
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
Copyright (c) 2014, magic labs GmbH
|
2
|
+
All rights reserved.
|
3
|
+
|
4
|
+
Redistribution and use in source and binary forms, with or without modification,
|
5
|
+
are permitted provided that the following conditions are met:
|
6
|
+
|
7
|
+
Redistributions of source code must retain the above copyright notice, this
|
8
|
+
list of conditions and the following disclaimer.
|
9
|
+
|
10
|
+
Redistributions in binary form must reproduce the above copyright notice, this
|
11
|
+
list of conditions and the following disclaimer in the documentation and/or
|
12
|
+
other materials provided with the distribution.
|
13
|
+
|
14
|
+
Neither the name of the magic labs GmbH nor the names of its
|
15
|
+
contributors may be used to endorse or promote products derived from
|
16
|
+
this software without specific prior written permission.
|
17
|
+
|
18
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
19
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
20
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
21
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
22
|
+
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
23
|
+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
24
|
+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
25
|
+
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
26
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
27
|
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/README.md
ADDED
@@ -0,0 +1,136 @@
|
|
1
|
+
[](https://travis-ci.org/AlchemyCMS/alchemy-pg_search)
|
2
|
+
|
3
|
+
# Alchemy CMS Postgresql Fulltext Search
|
4
|
+
|
5
|
+
This gem provides full text search for projects using postgresql databases to Alchemy CMS 3.3 and above.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's `Gemfile`:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
gem 'alchemy-pg_search', github: 'AlchemyCMS/alchemy-pg_search', branch: 'master'
|
13
|
+
```
|
14
|
+
|
15
|
+
And then execute:
|
16
|
+
|
17
|
+
```shell
|
18
|
+
$ bundle install
|
19
|
+
```
|
20
|
+
|
21
|
+
Add migrations:
|
22
|
+
|
23
|
+
```shell
|
24
|
+
$ bin/rake alchemy_pg_search:install:migrations
|
25
|
+
$ bin/rake db:migrate
|
26
|
+
```
|
27
|
+
|
28
|
+
## Usage
|
29
|
+
|
30
|
+
Every `EssenceText`, `EssenceRichtext` and `EssencePicture` will be indexed unless you tell Alchemy to not index a specific content.
|
31
|
+
|
32
|
+
### Disable Indexing
|
33
|
+
|
34
|
+
Simply pass `searchable: false` to your content definitions and Alchemy will not index results from that particular content.
|
35
|
+
|
36
|
+
```yaml
|
37
|
+
# elements.yml
|
38
|
+
- name: secrets
|
39
|
+
contents:
|
40
|
+
- name: passwords
|
41
|
+
type: EssenceText
|
42
|
+
searchable: false
|
43
|
+
default: 'This is my secret password.'
|
44
|
+
```
|
45
|
+
|
46
|
+
### Rendering search results.
|
47
|
+
|
48
|
+
In order to render the search results, you'll need a page layout that represents the search result page. Simply mark a page layout as `searchresults: true`. The search form will pick this page as result page.
|
49
|
+
|
50
|
+
#### Search Results Page
|
51
|
+
|
52
|
+
```yaml
|
53
|
+
# page_layouts.yml
|
54
|
+
- name: search
|
55
|
+
searchresults: true
|
56
|
+
unique: true
|
57
|
+
```
|
58
|
+
|
59
|
+
Tip: For maximum flexibility you could also add an element that represents the search results. This lets your editors to place additional elements (maybe a header image or additional text blocks) on the search result page.
|
60
|
+
|
61
|
+
```yaml
|
62
|
+
# page_layouts.yml
|
63
|
+
- name: search
|
64
|
+
searchresults: true
|
65
|
+
unique: true
|
66
|
+
elements:
|
67
|
+
- searchresults
|
68
|
+
autogenerate:
|
69
|
+
- searchresults
|
70
|
+
|
71
|
+
# elements.yml
|
72
|
+
- name: searchresults
|
73
|
+
unique: true
|
74
|
+
```
|
75
|
+
|
76
|
+
and then use the view helpers to render the search form on the page layout partial and the search results on the element view partial.
|
77
|
+
|
78
|
+
### View Helpers
|
79
|
+
|
80
|
+
This gem provides some helper methods that let you render the form and the search results.
|
81
|
+
|
82
|
+
* Render the search form:
|
83
|
+
`render_search_form`
|
84
|
+
|
85
|
+
* Render the search results:
|
86
|
+
`render_search_results`
|
87
|
+
|
88
|
+
### Customize Views
|
89
|
+
|
90
|
+
If you want to override the search form and search result views please use this generator.
|
91
|
+
|
92
|
+
```shell
|
93
|
+
$ bin/rails g alchemy:pg_search:views
|
94
|
+
```
|
95
|
+
|
96
|
+
### Translating Views
|
97
|
+
|
98
|
+
The views are fully translatable. German and english translations are already provided with this gem.
|
99
|
+
|
100
|
+
If you want add your own translation, just place a locale file into your projects `config/locales` folder.
|
101
|
+
|
102
|
+
Here is the english example:
|
103
|
+
|
104
|
+
```yaml
|
105
|
+
en:
|
106
|
+
alchemy:
|
107
|
+
|
108
|
+
search_form:
|
109
|
+
placeholder: 'Search query'
|
110
|
+
submit: 'Search'
|
111
|
+
|
112
|
+
search_result_page:
|
113
|
+
result_page: Page
|
114
|
+
no_results: "Your search for '%{query}' offers no result"
|
115
|
+
result_heading: "Your search for '%{query}'"
|
116
|
+
result_count:
|
117
|
+
one: 'Offers one result'
|
118
|
+
other: 'Offers %{count} results'
|
119
|
+
```
|
120
|
+
|
121
|
+
## Upgrading
|
122
|
+
|
123
|
+
If you are upgrading from an old Alchemy < 3.0 based project that uses the ferret based full text search, please run this handy generator:
|
124
|
+
|
125
|
+
```shell
|
126
|
+
$ bin/rails g alchemy:pg_search:upgrade
|
127
|
+
$ bin/rake db:migrate
|
128
|
+
```
|
129
|
+
|
130
|
+
## Contributing
|
131
|
+
|
132
|
+
1. Fork it
|
133
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
134
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
135
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
136
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
begin
|
3
|
+
require 'bundler/setup'
|
4
|
+
rescue LoadError
|
5
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
6
|
+
end
|
7
|
+
|
8
|
+
begin
|
9
|
+
require 'rdoc/task'
|
10
|
+
rescue LoadError
|
11
|
+
require 'rdoc/rdoc'
|
12
|
+
require 'rake/rdoctask'
|
13
|
+
RDoc::Task = Rake::RDocTask
|
14
|
+
end
|
15
|
+
|
16
|
+
desc 'Generate documentation for Alchemy CMS.'
|
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')
|
24
|
+
end
|
25
|
+
|
26
|
+
APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__)
|
27
|
+
load 'rails/tasks/engine.rake'
|
28
|
+
|
29
|
+
require 'rspec/core'
|
30
|
+
require 'rspec/core/rake_task'
|
31
|
+
|
32
|
+
RSpec::Core::RakeTask.new(:spec)
|
33
|
+
|
34
|
+
task :default => ['alchemy:spec:prepare', :spec]
|
35
|
+
|
36
|
+
Bundler::GemHelper.install_tasks
|
37
|
+
|
38
|
+
namespace :alchemy do
|
39
|
+
namespace :spec do
|
40
|
+
|
41
|
+
desc "Prepares database for testing"
|
42
|
+
task :prepare do
|
43
|
+
system 'cd spec/dummy; RAILS_ENV=test bin/rake db:setup db:seed; cd -'
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'alchemy/pg_search/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "alchemy-pg_search"
|
8
|
+
spec.version = Alchemy::PgSearch::VERSION
|
9
|
+
spec.authors = ["Thomas von Deyen"]
|
10
|
+
spec.email = ["alchemy@magiclabs.de"]
|
11
|
+
spec.description = %q{PostgreSQL search for Alchemy CMS 3.0}
|
12
|
+
spec.summary = %q{This gem provides PostgreSQL full text search to Alchemy 3.0}
|
13
|
+
spec.homepage = "http://alchemy-cms.com"
|
14
|
+
spec.license = "BSD"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^spec/}) }
|
17
|
+
spec.require_paths = ["lib"]
|
18
|
+
|
19
|
+
spec.add_runtime_dependency "alchemy_cms", ["> 3.2", "< 4.0"]
|
20
|
+
spec.add_runtime_dependency "pg_search", ["~> 0.7"]
|
21
|
+
spec.add_runtime_dependency "pg"
|
22
|
+
|
23
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
24
|
+
spec.add_development_dependency "rake"
|
25
|
+
spec.add_development_dependency "rspec-rails"
|
26
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Alchemy
|
2
|
+
module PgSearch
|
3
|
+
module SearchHelper
|
4
|
+
|
5
|
+
# Renders a search form
|
6
|
+
#
|
7
|
+
# It queries the controller and then redirects to the search result page.
|
8
|
+
#
|
9
|
+
# === Example search results page layout
|
10
|
+
#
|
11
|
+
# Only performs the search if ferret is enabled in your +config/alchemy/config.yml+ and
|
12
|
+
# a page is present that is flagged with +searchresults+ true.
|
13
|
+
#
|
14
|
+
# # config/alchemy/page_layouts.yml
|
15
|
+
# - name: search
|
16
|
+
# searchresults: true # Flag as search result page
|
17
|
+
#
|
18
|
+
# === Note
|
19
|
+
#
|
20
|
+
# The search result page will not be cached.
|
21
|
+
#
|
22
|
+
# @option options html5 [Boolean] (true) Should the search form be of type search or not?
|
23
|
+
# @option options class [String] (fulltext_search) The default css class of the form
|
24
|
+
# @option options id [String] (search) The default css id of the form
|
25
|
+
#
|
26
|
+
def render_search_form(options={})
|
27
|
+
default_options = {
|
28
|
+
html5: false,
|
29
|
+
class: 'fulltext_search',
|
30
|
+
id: 'search'
|
31
|
+
}
|
32
|
+
render 'alchemy/search/form', options: default_options.merge(options), search_result_page: search_result_page
|
33
|
+
end
|
34
|
+
|
35
|
+
# Renders the search results partial within +app/views/alchemy/search/_results.html+
|
36
|
+
#
|
37
|
+
# @option options show_result_count [Boolean] (true) Should the count of results be displayed or not?
|
38
|
+
# @option options show_heading [Boolean] (true) Should the heading be displayed or not?
|
39
|
+
#
|
40
|
+
def render_search_results(options={})
|
41
|
+
default_options = {
|
42
|
+
show_result_count: true,
|
43
|
+
show_heading: true
|
44
|
+
}
|
45
|
+
render 'alchemy/search/results', options: default_options.merge(options)
|
46
|
+
end
|
47
|
+
|
48
|
+
def highlighted_excerpt(text, phrase, radius = 50)
|
49
|
+
highlight(excerpt(text, phrase, radius: radius).to_s, phrase)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
<% if search_result_page %>
|
2
|
+
<%= form_tag show_alchemy_page_path(search_result_page),
|
3
|
+
method: 'get', id: options[:id], class: options[:class] do %>
|
4
|
+
<% if options[:html5] %>
|
5
|
+
<%= search_field_tag :query, params[:query],
|
6
|
+
placeholder: Alchemy.t(:placeholder, scope: 'search_form') %>
|
7
|
+
<% else %>
|
8
|
+
<%= text_field_tag(:query, params[:query]) %>
|
9
|
+
<% end %>
|
10
|
+
<%= submit_tag Alchemy.t(:submit, scope: 'search_form'), name: nil %>
|
11
|
+
<% end %>
|
12
|
+
<% end %>
|
@@ -0,0 +1,13 @@
|
|
1
|
+
<li class="search_result">
|
2
|
+
<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 %>
|
8
|
+
<% end %>
|
9
|
+
<% else %>
|
10
|
+
<p><%= page.meta_description %></p>
|
11
|
+
<% end %>
|
12
|
+
<p><%= link_to page.urlname, show_alchemy_page_path(page) %></p>
|
13
|
+
</li>
|
@@ -0,0 +1,26 @@
|
|
1
|
+
<div class="search_results">
|
2
|
+
<% if @search_results.blank? %>
|
3
|
+
<h2 class="no_search_results">
|
4
|
+
<%= raw Alchemy.t('search_result_page.no_results', query: h(params[:query])) %>
|
5
|
+
</h2>
|
6
|
+
<% else %>
|
7
|
+
<h2 class="search_results_heading">
|
8
|
+
<%= raw Alchemy.t("search_result_page.result_heading", query: h(params[:query])) %>
|
9
|
+
<%= Alchemy.t("search_result_page.result_count",
|
10
|
+
count: @search_results.try(:total_count) || @search_results.size) %>
|
11
|
+
</h2>
|
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 %>
|
20
|
+
</ul>
|
21
|
+
<% end %>
|
22
|
+
</div>
|
23
|
+
|
24
|
+
<% if @search_results.try(:total_pages) %>
|
25
|
+
<%= paginate @search_results %>
|
26
|
+
<% end %>
|
data/bin/rails
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# This command will automatically be run when you run "rails" with Rails 4 gems installed from the root of your application.
|
3
|
+
|
4
|
+
ENGINE_ROOT = File.expand_path('../..', __FILE__)
|
5
|
+
ENGINE_PATH = File.expand_path('../../lib/alchemy_pg_search/engine', __FILE__)
|
6
|
+
|
7
|
+
# Set up gems listed in the Gemfile.
|
8
|
+
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
|
9
|
+
require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
|
10
|
+
|
11
|
+
require 'rails/all'
|
12
|
+
require 'rails/engine/commands'
|
@@ -0,0 +1,14 @@
|
|
1
|
+
de:
|
2
|
+
alchemy:
|
3
|
+
|
4
|
+
search_form:
|
5
|
+
placeholder: 'Suchbegriff'
|
6
|
+
submit: 'Suchen'
|
7
|
+
|
8
|
+
search_result_page:
|
9
|
+
result_page: Seite
|
10
|
+
no_results: "Ihre Suche nach '%{query}' ergab keine Treffer."
|
11
|
+
result_heading: "Ihre Suche nach '%{query}'"
|
12
|
+
result_count:
|
13
|
+
one: "ergab einen Treffer"
|
14
|
+
other: "ergab %{count} Treffer"
|
@@ -0,0 +1,14 @@
|
|
1
|
+
en:
|
2
|
+
alchemy:
|
3
|
+
|
4
|
+
search_form:
|
5
|
+
placeholder: 'Search query'
|
6
|
+
submit: 'Search'
|
7
|
+
|
8
|
+
search_result_page:
|
9
|
+
result_page: Page
|
10
|
+
no_results: "Your search for '%{query}' returned no result"
|
11
|
+
result_heading: "Your search for '%{query}'"
|
12
|
+
result_count:
|
13
|
+
one: 'returned one result'
|
14
|
+
other: 'returned %{count} results'
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require "alchemy/pg_search/engine"
|
2
|
+
require "alchemy/pg_search/config"
|
3
|
+
require "alchemy/pg_search/page_search_scope"
|
4
|
+
|
5
|
+
module Alchemy
|
6
|
+
module PgSearch
|
7
|
+
SEARCHABLE_ESSENCES = %w(EssenceText EssenceRichtext EssencePicture)
|
8
|
+
DEFAULT_CONFIG = {
|
9
|
+
page_search_scope: PageSearchScope.new
|
10
|
+
}
|
11
|
+
|
12
|
+
extend Config
|
13
|
+
self.config = DEFAULT_CONFIG
|
14
|
+
|
15
|
+
def self.is_searchable_essence?(essence_type)
|
16
|
+
SEARCHABLE_ESSENCES.include?(essence_type)
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.searchable_essence_classes
|
20
|
+
SEARCHABLE_ESSENCES.map { |k| "Alchemy::#{k.classify}".constantize }
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
Alchemy::Content.class_eval do
|
2
|
+
|
3
|
+
# Prepares the attributes for creating the essence.
|
4
|
+
#
|
5
|
+
# 1. It sets a default text if given in +elements.yml+
|
6
|
+
# 2. It sets searchable value for EssenceText, EssencePicture and EssenceRichtext essences
|
7
|
+
#
|
8
|
+
def prepared_attributes_for_essence
|
9
|
+
attributes = {
|
10
|
+
ingredient: default_text(definition['default'])
|
11
|
+
}
|
12
|
+
if Alchemy::PgSearch.is_searchable_essence?(definition['type'])
|
13
|
+
attributes.merge!(searchable: definition.fetch('searchable', true))
|
14
|
+
end
|
15
|
+
attributes
|
16
|
+
end
|
17
|
+
|
18
|
+
def searchable_ingredient
|
19
|
+
case essence_type
|
20
|
+
when 'Alchemy::EssencePicture'
|
21
|
+
then essence.caption
|
22
|
+
when 'Alchemy::EssenceRichtext'
|
23
|
+
then essence.stripped_body
|
24
|
+
when 'Alchemy::EssenceText'
|
25
|
+
then essence.body
|
26
|
+
else
|
27
|
+
ingredient
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,92 @@
|
|
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
|
+
if self.class == Alchemy::Admin::PagesController && params[:query].blank?
|
38
|
+
params[:query] = 'lorem'
|
39
|
+
end
|
40
|
+
return if params[:query].blank?
|
41
|
+
@search_results = search_results
|
42
|
+
if paginate_per
|
43
|
+
@search_results = @search_results.page(params[:page]).per(paginate_per)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# Find Pages that have what is provided in "query" param with PgSearch
|
48
|
+
#
|
49
|
+
# @return [Array]
|
50
|
+
#
|
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
|
60
|
+
end
|
61
|
+
|
62
|
+
# A view helper that loads the search result page.
|
63
|
+
#
|
64
|
+
# @return [Alchemy::Page]
|
65
|
+
#
|
66
|
+
def search_result_page
|
67
|
+
@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"
|
74
|
+
end
|
75
|
+
page
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def search_result_page_layout
|
80
|
+
page_layout = PageLayout.get_all_by_attributes(searchresults: true).first
|
81
|
+
if page_layout.nil?
|
82
|
+
raise "No searchresults page layout found. Please add page layout with `searchresults: true` into your `page_layouts.yml` file."
|
83
|
+
end
|
84
|
+
page_layout
|
85
|
+
end
|
86
|
+
|
87
|
+
def paginate_per
|
88
|
+
Alchemy::PgSearch.config[:paginate_per]
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
Alchemy::Element.class_eval do
|
2
|
+
include PgSearch
|
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
|
+
-> { joins(:element).where(searchable: true, alchemy_elements: {public: true}) },
|
16
|
+
class_name: 'Alchemy::EssenceText',
|
17
|
+
source_type: 'Alchemy::EssenceText',
|
18
|
+
through: :contents,
|
19
|
+
source: :essence
|
20
|
+
|
21
|
+
has_many :searchable_essence_richtexts,
|
22
|
+
-> { joins(:element).where(searchable: true, alchemy_elements: {public: true}) },
|
23
|
+
class_name: 'Alchemy::EssenceRichtext',
|
24
|
+
source_type: 'Alchemy::EssenceRichtext',
|
25
|
+
through: :contents,
|
26
|
+
source: :essence
|
27
|
+
|
28
|
+
has_many :searchable_essence_pictures,
|
29
|
+
-> { joins(:element).where(searchable: true, alchemy_elements: {public: true}) },
|
30
|
+
class_name: 'Alchemy::EssencePicture',
|
31
|
+
source_type: 'Alchemy::EssencePicture',
|
32
|
+
through: :contents,
|
33
|
+
source: :essence
|
34
|
+
|
35
|
+
has_many :searchable_contents,
|
36
|
+
-> { where(essence_type: ['Alchemy::EssenceText', 'Alchemy::EssenceRichtext', 'Alchemy::EssencePicture']) },
|
37
|
+
class_name: 'Alchemy::Content',
|
38
|
+
source: :contents
|
39
|
+
|
40
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'alchemy_cms'
|
2
|
+
require 'pg_search'
|
3
|
+
|
4
|
+
module Alchemy
|
5
|
+
module PgSearch
|
6
|
+
class Engine < ::Rails::Engine
|
7
|
+
engine_name 'alchemy_pg_search'
|
8
|
+
|
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"
|
14
|
+
require_relative './searchable'
|
15
|
+
|
16
|
+
# We need to have the search methods present in all Alchemy controllers
|
17
|
+
Alchemy::BaseController.send(:include, Alchemy::PgSearch::ControllerMethods)
|
18
|
+
|
19
|
+
# Inject searchable attribute persistence into searchable essence classes
|
20
|
+
Alchemy::PgSearch.searchable_essence_classes.each do |klass|
|
21
|
+
klass.send(:include, Alchemy::PgSearch::Searchable)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
Alchemy::Page.class_eval do
|
2
|
+
include PgSearch
|
3
|
+
|
4
|
+
# Enable Postgresql full text indexing.
|
5
|
+
#
|
6
|
+
pg_search_scope :full_text_search, against: {
|
7
|
+
meta_description: 'B',
|
8
|
+
meta_keywords: 'B',
|
9
|
+
title: 'B',
|
10
|
+
name: 'A'
|
11
|
+
},
|
12
|
+
associated_against: {
|
13
|
+
searchable_essence_texts: :body,
|
14
|
+
searchable_essence_richtexts: :stripped_body,
|
15
|
+
searchable_essence_pictures: :caption
|
16
|
+
},
|
17
|
+
using: {
|
18
|
+
tsearch: {prefix: true}
|
19
|
+
}
|
20
|
+
|
21
|
+
has_many :searchable_essence_texts,
|
22
|
+
-> { where(searchable: true, alchemy_elements: {public: true}) },
|
23
|
+
class_name: 'Alchemy::EssenceText',
|
24
|
+
source_type: 'Alchemy::EssenceText',
|
25
|
+
through: :descendent_contents,
|
26
|
+
source: :essence
|
27
|
+
|
28
|
+
has_many :searchable_essence_richtexts,
|
29
|
+
-> { where(searchable: true, alchemy_elements: {public: true}) },
|
30
|
+
class_name: 'Alchemy::EssenceRichtext',
|
31
|
+
source_type: 'Alchemy::EssenceRichtext',
|
32
|
+
through: :descendent_contents,
|
33
|
+
source: :essence
|
34
|
+
|
35
|
+
has_many :searchable_essence_pictures,
|
36
|
+
-> { where(searchable: true, alchemy_elements: {public: true}) },
|
37
|
+
class_name: 'Alchemy::EssencePicture',
|
38
|
+
source_type: 'Alchemy::EssencePicture',
|
39
|
+
through: :descendent_contents,
|
40
|
+
source: :essence
|
41
|
+
|
42
|
+
def element_search_results(query)
|
43
|
+
descendent_elements.full_text_search(query)
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Alchemy
|
2
|
+
module PgSearch
|
3
|
+
|
4
|
+
# Ensures that the current content definition value for +searchable+ gets persisted.
|
5
|
+
#
|
6
|
+
# It is enabled per default, but you can disable indexing in your +elements.yml+ file.
|
7
|
+
#
|
8
|
+
# === Example
|
9
|
+
#
|
10
|
+
# name: secrets
|
11
|
+
# contents:
|
12
|
+
# - name: confidential
|
13
|
+
# type: EssenceRichtext
|
14
|
+
# searchable: false
|
15
|
+
#
|
16
|
+
module Searchable
|
17
|
+
extend ActiveSupport::Concern
|
18
|
+
|
19
|
+
included do
|
20
|
+
before_update do
|
21
|
+
write_attribute(:searchable, definition.fetch('searchable', true))
|
22
|
+
true
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,31 @@
|
|
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
|
@@ -0,0 +1,20 @@
|
|
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
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'rails'
|
2
|
+
|
3
|
+
module Alchemy
|
4
|
+
module PgSearch
|
5
|
+
class ViewsGenerator < ::Rails::Generators::Base
|
6
|
+
desc "This generator copies the search form and result views into your project."
|
7
|
+
|
8
|
+
source_root File.expand_path("../../../../../app/views/alchemy", File.dirname(__FILE__))
|
9
|
+
|
10
|
+
def copy_views
|
11
|
+
directory 'search', Rails.root.join('app/views/alchemy/search')
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
metadata
ADDED
@@ -0,0 +1,166 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: alchemy-pg_search
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.2.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Thomas von Deyen
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-11-28 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: alchemy_cms
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '3.2'
|
20
|
+
- - "<"
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '4.0'
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - ">"
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '3.2'
|
30
|
+
- - "<"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '4.0'
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: pg_search
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - "~>"
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '0.7'
|
40
|
+
type: :runtime
|
41
|
+
prerelease: false
|
42
|
+
version_requirements: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - "~>"
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '0.7'
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: pg
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '0'
|
61
|
+
- !ruby/object:Gem::Dependency
|
62
|
+
name: bundler
|
63
|
+
requirement: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - "~>"
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '1.3'
|
68
|
+
type: :development
|
69
|
+
prerelease: false
|
70
|
+
version_requirements: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - "~>"
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '1.3'
|
75
|
+
- !ruby/object:Gem::Dependency
|
76
|
+
name: rake
|
77
|
+
requirement: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - ">="
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '0'
|
82
|
+
type: :development
|
83
|
+
prerelease: false
|
84
|
+
version_requirements: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - ">="
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '0'
|
89
|
+
- !ruby/object:Gem::Dependency
|
90
|
+
name: rspec-rails
|
91
|
+
requirement: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - ">="
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '0'
|
96
|
+
type: :development
|
97
|
+
prerelease: false
|
98
|
+
version_requirements: !ruby/object:Gem::Requirement
|
99
|
+
requirements:
|
100
|
+
- - ">="
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: '0'
|
103
|
+
description: PostgreSQL search for Alchemy CMS 3.0
|
104
|
+
email:
|
105
|
+
- alchemy@magiclabs.de
|
106
|
+
executables: []
|
107
|
+
extensions: []
|
108
|
+
extra_rdoc_files: []
|
109
|
+
files:
|
110
|
+
- ".gitignore"
|
111
|
+
- ".travis.yml"
|
112
|
+
- CHANGELOG.md
|
113
|
+
- Gemfile
|
114
|
+
- LICENSE
|
115
|
+
- README.md
|
116
|
+
- Rakefile
|
117
|
+
- alchemy-pg_search.gemspec
|
118
|
+
- app/helpers/alchemy/pg_search/search_helper.rb
|
119
|
+
- app/views/alchemy/search/_form.html.erb
|
120
|
+
- app/views/alchemy/search/_result.html.erb
|
121
|
+
- app/views/alchemy/search/_results.html.erb
|
122
|
+
- bin/rails
|
123
|
+
- config/locales/alchemy.search.de.yml
|
124
|
+
- config/locales/alchemy.search.en.yml
|
125
|
+
- db/migrate/20141211105526_add_searchable_to_alchemy_essence_texts.rb
|
126
|
+
- db/migrate/20141211105942_add_searchable_to_alchemy_essence_richtexts.rb
|
127
|
+
- db/migrate/20141211110126_add_searchable_to_alchemy_essence_pictures.rb
|
128
|
+
- lib/alchemy-pg_search.rb
|
129
|
+
- lib/alchemy/pg_search/config.rb
|
130
|
+
- lib/alchemy/pg_search/content_extension.rb
|
131
|
+
- lib/alchemy/pg_search/controller_methods.rb
|
132
|
+
- lib/alchemy/pg_search/element_extension.rb
|
133
|
+
- lib/alchemy/pg_search/engine.rb
|
134
|
+
- lib/alchemy/pg_search/page_extension.rb
|
135
|
+
- lib/alchemy/pg_search/page_search_scope.rb
|
136
|
+
- lib/alchemy/pg_search/searchable.rb
|
137
|
+
- lib/alchemy/pg_search/version.rb
|
138
|
+
- lib/generators/alchemy/pg_search/upgrade/templates/migration.rb.tt
|
139
|
+
- lib/generators/alchemy/pg_search/upgrade/upgrade_generator.rb
|
140
|
+
- lib/generators/alchemy/pg_search/views/views_generator.rb
|
141
|
+
- lib/tasks/alchemy/pg_search_tasks.rake
|
142
|
+
homepage: http://alchemy-cms.com
|
143
|
+
licenses:
|
144
|
+
- BSD
|
145
|
+
metadata: {}
|
146
|
+
post_install_message:
|
147
|
+
rdoc_options: []
|
148
|
+
require_paths:
|
149
|
+
- lib
|
150
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
151
|
+
requirements:
|
152
|
+
- - ">="
|
153
|
+
- !ruby/object:Gem::Version
|
154
|
+
version: '0'
|
155
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - ">="
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: '0'
|
160
|
+
requirements: []
|
161
|
+
rubyforge_project:
|
162
|
+
rubygems_version: 2.7.6
|
163
|
+
signing_key:
|
164
|
+
specification_version: 4
|
165
|
+
summary: This gem provides PostgreSQL full text search to Alchemy 3.0
|
166
|
+
test_files: []
|