alchemy-pg_search 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Build Status](https://travis-ci.org/AlchemyCMS/alchemy-pg_search.svg?branch=master)](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: []
|