pg_search 2.3.0 → 2.3.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.codeclimate.yml +1 -0
- data/.github/dependabot.yml +11 -0
- data/.github/workflows/ci.yml +75 -0
- data/.jrubyrc +1 -0
- data/.rubocop.yml +88 -7
- data/.travis.yml +20 -33
- data/CHANGELOG.md +50 -16
- data/CODE_OF_CONDUCT.md +76 -0
- data/Gemfile +1 -1
- data/LICENSE +1 -1
- data/README.md +73 -26
- data/Rakefile +7 -1
- data/lib/pg_search/configuration.rb +12 -2
- data/lib/pg_search/document.rb +1 -1
- data/lib/pg_search/features/dmetaphone.rb +4 -6
- data/lib/pg_search/features/feature.rb +1 -1
- data/lib/pg_search/features/tsearch.rb +14 -13
- data/lib/pg_search/migration/templates/add_pg_search_dmetaphone_support_functions.rb.erb +6 -6
- data/lib/pg_search/migration/templates/create_pg_search_documents.rb.erb +2 -2
- data/lib/pg_search/multisearch/rebuilder.rb +7 -3
- data/lib/pg_search/multisearch.rb +21 -4
- data/lib/pg_search/scope_options.rb +5 -8
- data/lib/pg_search/tasks.rb +2 -1
- data/lib/pg_search/version.rb +1 -1
- data/lib/pg_search.rb +6 -8
- data/pg_search.gemspec +14 -7
- data/spec/.rubocop.yml +2 -2
- data/spec/integration/.rubocop.yml +11 -0
- data/spec/integration/associations_spec.rb +17 -56
- data/spec/integration/deprecation_spec.rb +1 -1
- data/spec/integration/pg_search_spec.rb +94 -52
- data/spec/lib/pg_search/configuration/association_spec.rb +8 -6
- data/spec/lib/pg_search/features/dmetaphone_spec.rb +2 -2
- data/spec/lib/pg_search/features/trigram_spec.rb +16 -12
- data/spec/lib/pg_search/features/tsearch_spec.rb +16 -10
- data/spec/lib/pg_search/multisearch/rebuilder_spec.rb +116 -71
- data/spec/lib/pg_search/multisearch_spec.rb +57 -29
- data/spec/lib/pg_search/multisearchable_spec.rb +150 -97
- data/spec/lib/pg_search/normalizer_spec.rb +12 -10
- data/spec/lib/pg_search_spec.rb +66 -55
- data/spec/spec_helper.rb +22 -5
- data/spec/support/database.rb +7 -5
- metadata +109 -19
- data/.autotest +0 -5
data/README.md
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
# [pg_search](http://github.com/Casecommons/pg_search/)
|
|
2
2
|
|
|
3
3
|
[](https://rubygems.org/gems/pg_search)
|
|
4
|
-
[](https://codeclimate.com/github/Casecommons/pg_search/maintainability)
|
|
6
|
-
[](https://codeclimate.com/github/Casecommons/pg_search/coverage)
|
|
7
|
-
[](http://inch-ci.org/github/Casecommons/pg_search)
|
|
4
|
+
[](https://github.com/Casecommons/pg_search/actions/workflows/ci.yml)
|
|
8
5
|
[](https://gitter.im/Casecommons/pg_search?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
|
9
6
|
|
|
10
7
|
## DESCRIPTION
|
|
@@ -12,12 +9,12 @@
|
|
|
12
9
|
PgSearch builds named scopes that take advantage of PostgreSQL's full text
|
|
13
10
|
search.
|
|
14
11
|
|
|
15
|
-
Read the blog post introducing PgSearch at https://
|
|
12
|
+
Read the blog post introducing PgSearch at https://tanzu.vmware.com/content/blog/pg-search-how-i-learned-to-stop-worrying-and-love-postgresql-full-text-search
|
|
16
13
|
|
|
17
14
|
## REQUIREMENTS
|
|
18
15
|
|
|
19
|
-
* Ruby 2.
|
|
20
|
-
* ActiveRecord
|
|
16
|
+
* Ruby 2.6+
|
|
17
|
+
* ActiveRecord 5.2+
|
|
21
18
|
* PostgreSQL 9.2+
|
|
22
19
|
* [PostgreSQL extensions](https://github.com/Casecommons/pg_search/wiki/Installing-PostgreSQL-Extensions) for certain features
|
|
23
20
|
|
|
@@ -53,6 +50,47 @@ class Shape < ActiveRecord::Base
|
|
|
53
50
|
end
|
|
54
51
|
```
|
|
55
52
|
|
|
53
|
+
### Contents
|
|
54
|
+
* [Multi-search vs. search scopes](#multi-search-vs-search-scopes)
|
|
55
|
+
* [Multi-search](#multi-search)
|
|
56
|
+
* [Setup](#setup)
|
|
57
|
+
* [`multisearchable`](#multisearchable)
|
|
58
|
+
* [More Options ](#more-options)
|
|
59
|
+
* [Multi-search associations](#multi-search-associations)
|
|
60
|
+
* [Searching in the global search index](#searching-in-the-global-search-index)
|
|
61
|
+
* [Chaining method calls onto the results](#chaining-method-calls-onto-the-results)
|
|
62
|
+
* [Configuring multi-search](#configuring-multi-search)
|
|
63
|
+
* [Rebuilding search documents for a given class](#rebuilding-search-documents-for-a-given-class)
|
|
64
|
+
* [Disabling multi-search indexing temporarily](#disabling-multi-search-indexing-temporarily)
|
|
65
|
+
* [`pg_search_scope`](#pg_search_scope)
|
|
66
|
+
* [Searching against one column](#searching-against-one-column)
|
|
67
|
+
* [Searching against multiple columns](#searching-against-multiple-columns)
|
|
68
|
+
* [Dynamic search scopes](#dynamic-search-scopes)
|
|
69
|
+
* [Searching through associations](#searching-through-associations)
|
|
70
|
+
* [Searching using different search features](#searching-using-different-search-features)
|
|
71
|
+
* [`:tsearch` (Full Text Search)](#tsearch-full-text-search)
|
|
72
|
+
* [Weighting](#weighting)
|
|
73
|
+
* [`:prefix` (PostgreSQL 8.4 and newer only)](#prefix-postgresql-84-and-newer-only)
|
|
74
|
+
* [`:negation`](#negation)
|
|
75
|
+
* [`:dictionary`](#dictionary)
|
|
76
|
+
* [`:normalization`](#normalization)
|
|
77
|
+
* [`:any_word`](#any_word)
|
|
78
|
+
* [`:sort_only`](#sort_only)
|
|
79
|
+
* [`:highlight`](#highlight)
|
|
80
|
+
* [`:dmetaphone` (Double Metaphone soundalike search)](#dmetaphone-double-metaphone-soundalike-search)
|
|
81
|
+
* [`:trigram` (Trigram search)](#trigram-trigram-search)
|
|
82
|
+
* [`:threshold`](#threshold)
|
|
83
|
+
* [`:word_similarity`](#word_similarity)
|
|
84
|
+
* [Limiting Fields When Combining Features](#limiting-fields-when-combining-features)
|
|
85
|
+
* [Ignoring accent marks](#ignoring-accent-marks)
|
|
86
|
+
* [Using tsvector columns](#using-tsvector-columns)
|
|
87
|
+
* [Combining multiple tsvectors](#combining-multiple-tsvectors)
|
|
88
|
+
* [Configuring ranking and ordering](#configuring-ranking-and-ordering)
|
|
89
|
+
* [`:ranked_by` (Choosing a ranking algorithm)](#ranked_by-choosing-a-ranking-algorithm)
|
|
90
|
+
* [`:order_within_rank` (Breaking ties)](#order_within_rank-breaking-ties)
|
|
91
|
+
* [`PgSearch#pg_search_rank` (Reading a record's rank as a Float)](#pgsearchpg_search_rank-reading-a-records-rank-as-a-float)
|
|
92
|
+
* [Search rank and chained scopes](#search-rank-and-chained-scopes)
|
|
93
|
+
|
|
56
94
|
### Multi-search vs. search scopes
|
|
57
95
|
|
|
58
96
|
pg_search supports two different techniques for searching, multi-search and
|
|
@@ -175,15 +213,17 @@ multisearchable(
|
|
|
175
213
|
|
|
176
214
|
**Specify additional attributes to be saved on the pg_search_documents table**
|
|
177
215
|
|
|
178
|
-
You can specify `:additional_attributes` to be saved within the pg_search_documents table. For example, perhaps you are indexing a book model and an article model and wanted to include the author_id.
|
|
216
|
+
You can specify `:additional_attributes` to be saved within the `pg_search_documents` table. For example, perhaps you are indexing a book model and an article model and wanted to include the author_id.
|
|
179
217
|
|
|
180
|
-
First, we need to add
|
|
218
|
+
First, we need to add a reference to author to the migration creating our `pg_search_documents` table.
|
|
181
219
|
|
|
182
220
|
```ruby
|
|
183
221
|
create_table :pg_search_documents do |t|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
222
|
+
t.text :content
|
|
223
|
+
t.references :author, index: true
|
|
224
|
+
t.belongs_to :searchable, polymorphic: true, index: true
|
|
225
|
+
t.timestamps null: false
|
|
226
|
+
end
|
|
187
227
|
```
|
|
188
228
|
|
|
189
229
|
Then, we can send in this additional attribute in a lambda
|
|
@@ -196,7 +236,10 @@ Then, we can send in this additional attribute in a lambda
|
|
|
196
236
|
```
|
|
197
237
|
|
|
198
238
|
This allows much faster searches without joins later on by doing something like:
|
|
199
|
-
|
|
239
|
+
|
|
240
|
+
```ruby
|
|
241
|
+
PgSearch.multisearch(params['search']).where(author_id: 2)
|
|
242
|
+
```
|
|
200
243
|
|
|
201
244
|
*NOTE: You must currently manually call `record.update_pg_search_document` for the additional attribute to be included in the pg_search_documents table*
|
|
202
245
|
|
|
@@ -273,7 +316,7 @@ To remove all of the documents for a given class, you can simply delete all of
|
|
|
273
316
|
the PgSearch::Document records.
|
|
274
317
|
|
|
275
318
|
```ruby
|
|
276
|
-
PgSearch::Document.
|
|
319
|
+
PgSearch::Document.delete_by(searchable_type: "Animal")
|
|
277
320
|
```
|
|
278
321
|
|
|
279
322
|
To regenerate the documents for a given class, run:
|
|
@@ -289,7 +332,14 @@ is your base class. You can prevent ```rebuild``` from deleting your records
|
|
|
289
332
|
like so:
|
|
290
333
|
|
|
291
334
|
```ruby
|
|
292
|
-
PgSearch::Multisearch.rebuild(Product, false)
|
|
335
|
+
PgSearch::Multisearch.rebuild(Product, clean_up: false)
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
```rebuild``` runs inside a single transaction. To run outside of a transaction,
|
|
339
|
+
you can pass ```transactional: false``` like so:
|
|
340
|
+
|
|
341
|
+
```ruby
|
|
342
|
+
PgSearch::Multisearch.rebuild(Product, transactional: false)
|
|
293
343
|
```
|
|
294
344
|
|
|
295
345
|
Rebuild is also available as a Rake task, for convenience.
|
|
@@ -333,11 +383,11 @@ class Movie < ActiveRecord::Base
|
|
|
333
383
|
|
|
334
384
|
# More sophisticated approach
|
|
335
385
|
def self.rebuild_pg_search_documents
|
|
336
|
-
connection.execute
|
|
386
|
+
connection.execute <<~SQL.squish
|
|
337
387
|
INSERT INTO pg_search_documents (searchable_type, searchable_id, content, created_at, updated_at)
|
|
338
388
|
SELECT 'Movie' AS searchable_type,
|
|
339
389
|
movies.id AS searchable_id,
|
|
340
|
-
(
|
|
390
|
+
CONCAT_WS(' ', movies.name, directors.name) AS content,
|
|
341
391
|
now() AS created_at,
|
|
342
392
|
now() AS updated_at
|
|
343
393
|
FROM movies
|
|
@@ -347,6 +397,7 @@ class Movie < ActiveRecord::Base
|
|
|
347
397
|
end
|
|
348
398
|
end
|
|
349
399
|
```
|
|
400
|
+
**Note:** If using PostgreSQL before 9.1, replace the `CONCAT_WS()` function call with double-pipe concatenation, eg. `(movies.name || ' ' || directors.name)`. However, now be aware that if *any* of the joined values is NULL then the final `content` value will also be NULL, whereas `CONCAT_WS()` will selectively ignore NULL values.
|
|
350
401
|
|
|
351
402
|
#### Disabling multi-search indexing temporarily
|
|
352
403
|
|
|
@@ -718,7 +769,7 @@ class Person < ActiveRecord::Base
|
|
|
718
769
|
pg_search_scope :search,
|
|
719
770
|
against: :name,
|
|
720
771
|
using: {
|
|
721
|
-
tsearch: {any_word: true}
|
|
772
|
+
tsearch: {any_word: true},
|
|
722
773
|
dmetaphone: {any_word: true, sort_only: true}
|
|
723
774
|
}
|
|
724
775
|
end
|
|
@@ -890,7 +941,7 @@ class Sentence < ActiveRecord::Base
|
|
|
890
941
|
include PgSearch::Model
|
|
891
942
|
|
|
892
943
|
pg_search_scope :similarity_like,
|
|
893
|
-
against: :
|
|
944
|
+
against: :name,
|
|
894
945
|
using: {
|
|
895
946
|
trigram: {
|
|
896
947
|
word_similarity: true
|
|
@@ -898,7 +949,7 @@ class Sentence < ActiveRecord::Base
|
|
|
898
949
|
}
|
|
899
950
|
|
|
900
951
|
pg_search_scope :word_similarity_like,
|
|
901
|
-
against: :
|
|
952
|
+
against: :name,
|
|
902
953
|
using: [:trigram]
|
|
903
954
|
end
|
|
904
955
|
|
|
@@ -1143,16 +1194,12 @@ Patterson](http://tenderlovemaking.com/) for the original version and to Caseboo
|
|
|
1143
1194
|
|
|
1144
1195
|
## CONTRIBUTIONS AND FEEDBACK
|
|
1145
1196
|
|
|
1146
|
-
|
|
1147
|
-
project](https://www.pivotaltracker.com/projects/228645) where we manage new
|
|
1148
|
-
feature ideas and bugs.
|
|
1197
|
+
Please read our [CONTRIBUTING guide](https://github.com/Casecommons/pg_search/blob/master/CONTRIBUTING.md).
|
|
1149
1198
|
|
|
1150
1199
|
We also have a [Google Group](http://groups.google.com/group/casecommons-dev)
|
|
1151
1200
|
for discussing pg_search and other Casebook PBC open source projects.
|
|
1152
1201
|
|
|
1153
|
-
Please read our [CONTRIBUTING guide](https://github.com/Casecommons/pg_search/blob/master/CONTRIBUTING.md).
|
|
1154
|
-
|
|
1155
1202
|
## LICENSE
|
|
1156
1203
|
|
|
1157
|
-
Copyright © 2010–
|
|
1204
|
+
Copyright © 2010–2021 [Casebook PBC](http://www.casebook.net).
|
|
1158
1205
|
Licensed under the MIT license, see [LICENSE](/LICENSE) file.
|
data/Rakefile
CHANGED
|
@@ -11,4 +11,10 @@ RuboCop::RakeTask.new do |t|
|
|
|
11
11
|
t.options = %w[--display-cop-names]
|
|
12
12
|
end
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
desc "Check test coverage"
|
|
15
|
+
task :undercover do
|
|
16
|
+
system("git fetch --unshallow") if ENV["CI"]
|
|
17
|
+
exit(1) unless system("bin/undercover --compare origin/master")
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
task default: %w[spec rubocop undercover]
|
|
@@ -92,8 +92,11 @@ module PgSearch
|
|
|
92
92
|
}.freeze
|
|
93
93
|
|
|
94
94
|
def assert_valid_options(options)
|
|
95
|
-
unless options[:against] || options[:associated_against]
|
|
96
|
-
raise
|
|
95
|
+
unless options[:against] || options[:associated_against] || using_tsvector_column?(options[:using])
|
|
96
|
+
raise(
|
|
97
|
+
ArgumentError,
|
|
98
|
+
"the search scope #{@name} must have :against, :associated_against, or :tsvector_column in its options"
|
|
99
|
+
)
|
|
97
100
|
end
|
|
98
101
|
|
|
99
102
|
options.assert_valid_keys(VALID_KEYS)
|
|
@@ -104,5 +107,12 @@ module PgSearch
|
|
|
104
107
|
end
|
|
105
108
|
end
|
|
106
109
|
end
|
|
110
|
+
|
|
111
|
+
def using_tsvector_column?(options)
|
|
112
|
+
return unless options.is_a?(Hash)
|
|
113
|
+
|
|
114
|
+
options.dig(:dmetaphone, :tsvector_column).present? ||
|
|
115
|
+
options.dig(:tsearch, :tsvector_column).present?
|
|
116
|
+
end
|
|
107
117
|
end
|
|
108
118
|
end
|
data/lib/pg_search/document.rb
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "active_support/core_ext/module/delegation"
|
|
4
|
+
|
|
3
5
|
module PgSearch
|
|
4
6
|
module Features
|
|
5
7
|
class DMetaphone
|
|
@@ -9,13 +11,9 @@ module PgSearch
|
|
|
9
11
|
@tsearch = TSearch.new(query, options, columns, model, dmetaphone_normalizer)
|
|
10
12
|
end
|
|
11
13
|
|
|
12
|
-
|
|
13
|
-
tsearch.conditions
|
|
14
|
-
end
|
|
14
|
+
delegate :conditions, to: :tsearch
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
tsearch.rank
|
|
18
|
-
end
|
|
16
|
+
delegate :rank, to: :tsearch
|
|
19
17
|
|
|
20
18
|
private
|
|
21
19
|
|
|
@@ -59,7 +59,7 @@ module PgSearch
|
|
|
59
59
|
end
|
|
60
60
|
end
|
|
61
61
|
|
|
62
|
-
def deprecated_headline_options
|
|
62
|
+
def deprecated_headline_options # rubocop:disable Metrics/MethodLength
|
|
63
63
|
indifferent_options = options.with_indifferent_access
|
|
64
64
|
|
|
65
65
|
%w[
|
|
@@ -97,7 +97,7 @@ module PgSearch
|
|
|
97
97
|
|
|
98
98
|
DISALLOWED_TSQUERY_CHARACTERS = /['?\\:‘’]/.freeze
|
|
99
99
|
|
|
100
|
-
def tsquery_for_term(unsanitized_term)
|
|
100
|
+
def tsquery_for_term(unsanitized_term)
|
|
101
101
|
if options[:negation] && unsanitized_term.start_with?("!")
|
|
102
102
|
unsanitized_term[0] = ''
|
|
103
103
|
negated = true
|
|
@@ -107,31 +107,32 @@ module PgSearch
|
|
|
107
107
|
|
|
108
108
|
term_sql = Arel.sql(normalize(connection.quote(sanitized_term)))
|
|
109
109
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
110
|
+
tsquery = tsquery_expression(term_sql, negated: negated, prefix: options[:prefix])
|
|
111
|
+
|
|
112
|
+
Arel::Nodes::NamedFunction.new("to_tsquery", [dictionary, tsquery]).to_sql
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# After this, the SQL expression evaluates to a string containing the term surrounded by single-quotes.
|
|
116
|
+
# If :prefix is true, then the term will have :* appended to the end.
|
|
117
|
+
# If :negated is true, then the term will have ! prepended to the front.
|
|
118
|
+
def tsquery_expression(term_sql, negated:, prefix:)
|
|
113
119
|
terms = [
|
|
114
120
|
(Arel::Nodes.build_quoted('!') if negated),
|
|
115
121
|
Arel::Nodes.build_quoted("' "),
|
|
116
122
|
term_sql,
|
|
117
123
|
Arel::Nodes.build_quoted(" '"),
|
|
118
|
-
(Arel::Nodes.build_quoted(":*") if
|
|
124
|
+
(Arel::Nodes.build_quoted(":*") if prefix)
|
|
119
125
|
].compact
|
|
120
126
|
|
|
121
|
-
|
|
127
|
+
terms.inject do |memo, term|
|
|
122
128
|
Arel::Nodes::InfixOperation.new("||", memo, Arel::Nodes.build_quoted(term))
|
|
123
129
|
end
|
|
124
|
-
|
|
125
|
-
Arel::Nodes::NamedFunction.new(
|
|
126
|
-
"to_tsquery",
|
|
127
|
-
[dictionary, tsquery_sql]
|
|
128
|
-
).to_sql
|
|
129
130
|
end
|
|
130
131
|
|
|
131
132
|
def tsquery
|
|
132
133
|
return "''" if query.blank?
|
|
133
134
|
|
|
134
|
-
query_terms = query.split
|
|
135
|
+
query_terms = query.split.compact
|
|
135
136
|
tsquery_terms = query_terms.map { |term| tsquery_for_term(term) }
|
|
136
137
|
tsquery_terms.join(options[:any_word] ? ' || ' : ' && ')
|
|
137
138
|
end
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
class AddPgSearchDmetaphoneSupportFunctions < ActiveRecord::Migration<%= migration_version %>
|
|
2
|
-
def
|
|
2
|
+
def up
|
|
3
3
|
say_with_time("Adding support functions for pg_search :dmetaphone") do
|
|
4
|
-
execute
|
|
5
|
-
<%= read_sql_file
|
|
4
|
+
execute <<~'SQL'.squish
|
|
5
|
+
<%= indent(read_sql_file("dmetaphone"), 8) %>
|
|
6
6
|
SQL
|
|
7
7
|
end
|
|
8
8
|
end
|
|
9
9
|
|
|
10
|
-
def
|
|
10
|
+
def down
|
|
11
11
|
say_with_time("Dropping support functions for pg_search :dmetaphone") do
|
|
12
|
-
execute
|
|
13
|
-
<%= read_sql_file
|
|
12
|
+
execute <<~'SQL'.squish
|
|
13
|
+
<%= indent(read_sql_file("uninstall_dmetaphone"), 8) %>
|
|
14
14
|
SQL
|
|
15
15
|
end
|
|
16
16
|
end
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
class CreatePgSearchDocuments < ActiveRecord::Migration<%= migration_version %>
|
|
2
|
-
def
|
|
2
|
+
def up
|
|
3
3
|
say_with_time("Creating table for pg_search multisearch") do
|
|
4
4
|
create_table :pg_search_documents do |t|
|
|
5
5
|
t.text :content
|
|
@@ -9,7 +9,7 @@ class CreatePgSearchDocuments < ActiveRecord::Migration<%= migration_version %>
|
|
|
9
9
|
end
|
|
10
10
|
end
|
|
11
11
|
|
|
12
|
-
def
|
|
12
|
+
def down
|
|
13
13
|
say_with_time("Dropping table for pg_search multisearch") do
|
|
14
14
|
drop_table :pg_search_documents
|
|
15
15
|
end
|
|
@@ -13,7 +13,7 @@ module PgSearch
|
|
|
13
13
|
def rebuild
|
|
14
14
|
if model.respond_to?(:rebuild_pg_search_documents)
|
|
15
15
|
model.rebuild_pg_search_documents
|
|
16
|
-
elsif conditional? || dynamic?
|
|
16
|
+
elsif conditional? || dynamic? || additional_attributes?
|
|
17
17
|
model.find_each(&:update_pg_search_document)
|
|
18
18
|
else
|
|
19
19
|
model.connection.execute(rebuild_sql)
|
|
@@ -30,7 +30,11 @@ module PgSearch
|
|
|
30
30
|
|
|
31
31
|
def dynamic?
|
|
32
32
|
column_names = model.columns.map(&:name)
|
|
33
|
-
columns.any? { |column|
|
|
33
|
+
columns.any? { |column| column_names.exclude?(column.to_s) }
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def additional_attributes?
|
|
37
|
+
model.pg_search_multisearchable_options.key?(:additional_attributes)
|
|
34
38
|
end
|
|
35
39
|
|
|
36
40
|
def connection
|
|
@@ -42,7 +46,7 @@ module PgSearch
|
|
|
42
46
|
end
|
|
43
47
|
|
|
44
48
|
def rebuild_sql_template
|
|
45
|
-
|
|
49
|
+
<<~SQL.squish
|
|
46
50
|
INSERT INTO :documents_table (searchable_type, searchable_id, content, created_at, updated_at)
|
|
47
51
|
SELECT :base_model_name AS searchable_type,
|
|
48
52
|
:model_table.#{primary_key} AS searchable_id,
|
|
@@ -5,16 +5,33 @@ require "pg_search/multisearch/rebuilder"
|
|
|
5
5
|
module PgSearch
|
|
6
6
|
module Multisearch
|
|
7
7
|
class << self
|
|
8
|
-
def rebuild(model,
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
def rebuild(model, deprecated_clean_up = nil, clean_up: true, transactional: true)
|
|
9
|
+
unless deprecated_clean_up.nil?
|
|
10
|
+
ActiveSupport::Deprecation.warn(
|
|
11
|
+
"pg_search 3.0 will no longer accept a boolean second argument to PgSearchMultisearch.rebuild, " \
|
|
12
|
+
"use keyword argument `clean_up:` instead."
|
|
13
|
+
)
|
|
14
|
+
clean_up = deprecated_clean_up
|
|
12
15
|
end
|
|
16
|
+
|
|
17
|
+
if transactional
|
|
18
|
+
model.transaction { execute(model, clean_up) }
|
|
19
|
+
else
|
|
20
|
+
execute(model, clean_up)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def execute(model, clean_up)
|
|
27
|
+
PgSearch::Document.where(searchable_type: model.base_class.name).delete_all if clean_up
|
|
28
|
+
Rebuilder.new(model).rebuild
|
|
13
29
|
end
|
|
14
30
|
end
|
|
15
31
|
|
|
16
32
|
class ModelNotMultisearchable < StandardError
|
|
17
33
|
def initialize(model_class)
|
|
34
|
+
super
|
|
18
35
|
@model_class = model_class
|
|
19
36
|
end
|
|
20
37
|
|
|
@@ -14,7 +14,7 @@ module PgSearch
|
|
|
14
14
|
|
|
15
15
|
def apply(scope)
|
|
16
16
|
scope = include_table_aliasing_for_rank(scope)
|
|
17
|
-
rank_table_alias = scope.pg_search_rank_table_alias(:
|
|
17
|
+
rank_table_alias = scope.pg_search_rank_table_alias(include_counter: true)
|
|
18
18
|
|
|
19
19
|
scope
|
|
20
20
|
.joins(rank_join(rank_table_alias))
|
|
@@ -37,11 +37,8 @@ module PgSearch
|
|
|
37
37
|
|
|
38
38
|
def with_pg_search_highlight
|
|
39
39
|
scope = self
|
|
40
|
-
scope.select(
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
def pg_search_highlight_field
|
|
44
|
-
"(#{highlight}) AS pg_search_highlight, #{table_name}.*"
|
|
40
|
+
scope = scope.select("#{table_name}.*") unless scope.select_values.any?
|
|
41
|
+
scope.select("(#{highlight}) AS pg_search_highlight")
|
|
45
42
|
end
|
|
46
43
|
|
|
47
44
|
def highlight
|
|
@@ -58,7 +55,7 @@ module PgSearch
|
|
|
58
55
|
end
|
|
59
56
|
|
|
60
57
|
module PgSearchRankTableAliasing
|
|
61
|
-
def pg_search_rank_table_alias(include_counter
|
|
58
|
+
def pg_search_rank_table_alias(include_counter: false)
|
|
62
59
|
components = [arel_table.name]
|
|
63
60
|
if include_counter
|
|
64
61
|
count = increment_counter
|
|
@@ -152,7 +149,7 @@ module PgSearch
|
|
|
152
149
|
return scope if scope.included_modules.include?(PgSearchRankTableAliasing)
|
|
153
150
|
|
|
154
151
|
scope.all.spawn.tap do |new_scope|
|
|
155
|
-
new_scope.
|
|
152
|
+
new_scope.instance_eval { extend PgSearchRankTableAliasing }
|
|
156
153
|
end
|
|
157
154
|
end
|
|
158
155
|
end
|
data/lib/pg_search/tasks.rb
CHANGED
|
@@ -7,11 +7,12 @@ namespace :pg_search do
|
|
|
7
7
|
namespace :multisearch do
|
|
8
8
|
desc "Rebuild PgSearch multisearch records for a given model"
|
|
9
9
|
task :rebuild, %i[model schema] => :environment do |_task, args|
|
|
10
|
-
raise ArgumentError,
|
|
10
|
+
raise ArgumentError, <<~MESSAGE unless args.model
|
|
11
11
|
|
|
12
12
|
You must pass a model as an argument.
|
|
13
13
|
Example: rake pg_search:multisearch:rebuild[BlogPost]
|
|
14
14
|
MESSAGE
|
|
15
|
+
|
|
15
16
|
model_class = args.model.classify.constantize
|
|
16
17
|
connection = PgSearch::Document.connection
|
|
17
18
|
original_schema_search_path = connection.schema_search_path
|
data/lib/pg_search/version.rb
CHANGED
data/lib/pg_search.rb
CHANGED
|
@@ -15,8 +15,10 @@ require "pg_search/scope_options"
|
|
|
15
15
|
require "pg_search/version"
|
|
16
16
|
|
|
17
17
|
module PgSearch
|
|
18
|
+
autoload :Document, "pg_search/document"
|
|
19
|
+
|
|
18
20
|
def self.included(base)
|
|
19
|
-
ActiveSupport::Deprecation.warn
|
|
21
|
+
ActiveSupport::Deprecation.warn <<~MESSAGE
|
|
20
22
|
Directly including `PgSearch` into an Active Record model is deprecated and will be removed in pg_search 3.0.
|
|
21
23
|
|
|
22
24
|
Please replace `include PgSearch` with `include PgSearch::Model`.
|
|
@@ -55,20 +57,16 @@ module PgSearch
|
|
|
55
57
|
class PgSearchRankNotSelected < StandardError
|
|
56
58
|
def message
|
|
57
59
|
"You must chain .with_pg_search_rank after the pg_search_scope " \
|
|
58
|
-
|
|
60
|
+
"to access the pg_search_rank attribute on returned records"
|
|
59
61
|
end
|
|
60
62
|
end
|
|
61
63
|
|
|
62
64
|
class PgSearchHighlightNotSelected < StandardError
|
|
63
65
|
def message
|
|
64
66
|
"You must chain .with_pg_search_highlight after the pg_search_scope " \
|
|
65
|
-
|
|
67
|
+
"to access the pg_search_highlight attribute on returned records"
|
|
66
68
|
end
|
|
67
69
|
end
|
|
68
70
|
end
|
|
69
71
|
|
|
70
|
-
|
|
71
|
-
require "pg_search/document"
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
require "pg_search/railtie" if defined?(Rails)
|
|
72
|
+
require "pg_search/railtie" if defined?(Rails::Railtie)
|
data/pg_search.gemspec
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
$LOAD_PATH.push File.expand_path('lib', __dir__)
|
|
4
4
|
require 'pg_search/version'
|
|
5
5
|
|
|
6
|
-
Gem::Specification.new do |s|
|
|
6
|
+
Gem::Specification.new do |s| # rubocop:disable Metrics/BlockLength
|
|
7
7
|
s.name = 'pg_search'
|
|
8
8
|
s.version = PgSearch::VERSION
|
|
9
9
|
s.platform = Gem::Platform::RUBY
|
|
@@ -13,21 +13,28 @@ Gem::Specification.new do |s|
|
|
|
13
13
|
s.summary = "PgSearch builds Active Record named scopes that take advantage of PostgreSQL's full text search"
|
|
14
14
|
s.description = "PgSearch builds Active Record named scopes that take advantage of PostgreSQL's full text search"
|
|
15
15
|
s.licenses = ['MIT']
|
|
16
|
+
s.metadata["rubygems_mfa_required"] = "true"
|
|
16
17
|
|
|
17
18
|
s.files = `git ls-files`.split("\n")
|
|
18
19
|
s.test_files = `git ls-files -- spec/*`.split("\n")
|
|
19
20
|
s.require_paths = ['lib']
|
|
20
21
|
|
|
21
|
-
s.add_dependency 'activerecord', '>=
|
|
22
|
-
s.add_dependency 'activesupport', '>=
|
|
22
|
+
s.add_dependency 'activerecord', '>= 5.2'
|
|
23
|
+
s.add_dependency 'activesupport', '>= 5.2'
|
|
23
24
|
|
|
24
25
|
s.add_development_dependency 'pry'
|
|
25
26
|
s.add_development_dependency 'rake'
|
|
26
|
-
s.add_development_dependency 'rspec'
|
|
27
|
-
s.add_development_dependency 'rubocop'
|
|
27
|
+
s.add_development_dependency 'rspec'
|
|
28
|
+
s.add_development_dependency 'rubocop'
|
|
28
29
|
s.add_development_dependency 'rubocop-performance'
|
|
30
|
+
s.add_development_dependency 'rubocop-rails'
|
|
31
|
+
s.add_development_dependency 'rubocop-rake'
|
|
32
|
+
s.add_development_dependency 'rubocop-rspec'
|
|
29
33
|
s.add_development_dependency 'simplecov'
|
|
30
|
-
s.add_development_dependency '
|
|
34
|
+
s.add_development_dependency 'simplecov-lcov'
|
|
35
|
+
s.add_development_dependency 'undercover'
|
|
36
|
+
s.add_development_dependency 'warning'
|
|
37
|
+
s.add_development_dependency 'with_model'
|
|
31
38
|
|
|
32
|
-
s.required_ruby_version = '>= 2.
|
|
39
|
+
s.required_ruby_version = '>= 2.6'
|
|
33
40
|
end
|
data/spec/.rubocop.yml
CHANGED