pg_search 2.3.0 → 2.3.5
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/.jrubyrc +1 -0
- data/.rubocop.yml +85 -7
- data/.travis.yml +14 -22
- data/CHANGELOG.md +41 -16
- data/CODE_OF_CONDUCT.md +76 -0
- data/Gemfile +1 -1
- data/LICENSE +1 -1
- data/README.md +60 -18
- data/lib/pg_search.rb +4 -6
- data/lib/pg_search/document.rb +1 -1
- data/lib/pg_search/features/dmetaphone.rb +4 -6
- data/lib/pg_search/features/tsearch.rb +13 -12
- 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.rb +10 -1
- data/lib/pg_search/multisearch/rebuilder.rb +7 -3
- data/lib/pg_search/scope_options.rb +3 -3
- data/lib/pg_search/tasks.rb +2 -1
- data/lib/pg_search/version.rb +1 -1
- data/pg_search.gemspec +11 -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 +62 -51
- 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 +15 -11
- 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 +48 -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 +13 -4
- data/spec/support/database.rb +1 -1
- metadata +78 -17
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`.
|
@@ -67,8 +69,4 @@ module PgSearch
|
|
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/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,25 +107,26 @@ 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
|
@@ -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
|
@@ -5,7 +5,15 @@ require "pg_search/multisearch/rebuilder"
|
|
5
5
|
module PgSearch
|
6
6
|
module Multisearch
|
7
7
|
class << self
|
8
|
-
def rebuild(model,
|
8
|
+
def rebuild(model, deprecated_clean_up = nil, clean_up: 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
|
15
|
+
end
|
16
|
+
|
9
17
|
model.transaction do
|
10
18
|
PgSearch::Document.where(searchable_type: model.base_class.name).delete_all if clean_up
|
11
19
|
Rebuilder.new(model).rebuild
|
@@ -15,6 +23,7 @@ module PgSearch
|
|
15
23
|
|
16
24
|
class ModelNotMultisearchable < StandardError
|
17
25
|
def initialize(model_class)
|
26
|
+
super
|
18
27
|
@model_class = model_class
|
19
28
|
end
|
20
29
|
|
@@ -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,
|
@@ -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))
|
@@ -58,7 +58,7 @@ module PgSearch
|
|
58
58
|
end
|
59
59
|
|
60
60
|
module PgSearchRankTableAliasing
|
61
|
-
def pg_search_rank_table_alias(include_counter
|
61
|
+
def pg_search_rank_table_alias(include_counter: false)
|
62
62
|
components = [arel_table.name]
|
63
63
|
if include_counter
|
64
64
|
count = increment_counter
|
@@ -152,7 +152,7 @@ module PgSearch
|
|
152
152
|
return scope if scope.included_modules.include?(PgSearchRankTableAliasing)
|
153
153
|
|
154
154
|
scope.all.spawn.tap do |new_scope|
|
155
|
-
new_scope.
|
155
|
+
new_scope.instance_eval { extend PgSearchRankTableAliasing }
|
156
156
|
end
|
157
157
|
end
|
158
158
|
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/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
|
@@ -18,16 +18,20 @@ Gem::Specification.new do |s|
|
|
18
18
|
s.test_files = `git ls-files -- spec/*`.split("\n")
|
19
19
|
s.require_paths = ['lib']
|
20
20
|
|
21
|
-
s.add_dependency 'activerecord', '>=
|
22
|
-
s.add_dependency 'activesupport', '>=
|
21
|
+
s.add_dependency 'activerecord', '>= 5.2'
|
22
|
+
s.add_dependency 'activesupport', '>= 5.2'
|
23
23
|
|
24
24
|
s.add_development_dependency 'pry'
|
25
25
|
s.add_development_dependency 'rake'
|
26
|
-
s.add_development_dependency 'rspec'
|
27
|
-
s.add_development_dependency 'rubocop'
|
26
|
+
s.add_development_dependency 'rspec'
|
27
|
+
s.add_development_dependency 'rubocop'
|
28
28
|
s.add_development_dependency 'rubocop-performance'
|
29
|
+
s.add_development_dependency 'rubocop-rails'
|
30
|
+
s.add_development_dependency 'rubocop-rake'
|
31
|
+
s.add_development_dependency 'rubocop-rspec'
|
29
32
|
s.add_development_dependency 'simplecov'
|
30
|
-
s.add_development_dependency '
|
33
|
+
s.add_development_dependency 'warning'
|
34
|
+
s.add_development_dependency 'with_model'
|
31
35
|
|
32
|
-
s.required_ruby_version = '>= 2.
|
36
|
+
s.required_ruby_version = '>= 2.5'
|
33
37
|
end
|
data/spec/.rubocop.yml
CHANGED
@@ -2,8 +2,9 @@
|
|
2
2
|
|
3
3
|
require "spec_helper"
|
4
4
|
|
5
|
-
|
6
|
-
|
5
|
+
# rubocop:disable RSpec/NestedGroups
|
6
|
+
describe "a pg_search_scope" do
|
7
|
+
context "when joining to another table" do
|
7
8
|
context "without an :against" do
|
8
9
|
with_model :AssociatedModel do
|
9
10
|
table do |t|
|
@@ -41,7 +42,7 @@ describe PgSearch do
|
|
41
42
|
end
|
42
43
|
end
|
43
44
|
|
44
|
-
context "
|
45
|
+
context "via a belongs_to association" do
|
45
46
|
with_model :AssociatedModel do
|
46
47
|
table do |t|
|
47
48
|
t.string 'title'
|
@@ -77,7 +78,7 @@ describe PgSearch do
|
|
77
78
|
end
|
78
79
|
end
|
79
80
|
|
80
|
-
context "
|
81
|
+
context "via a has_many association" do
|
81
82
|
with_model :AssociatedModelWithHasMany do
|
82
83
|
table do |t|
|
83
84
|
t.string 'title'
|
@@ -141,8 +142,8 @@ describe PgSearch do
|
|
141
142
|
end
|
142
143
|
end
|
143
144
|
|
144
|
-
context "across multiple associations" do
|
145
|
-
context "on different tables" do
|
145
|
+
context "when across multiple associations" do
|
146
|
+
context "when on different tables" do
|
146
147
|
with_model :FirstAssociatedModel do
|
147
148
|
table do |t|
|
148
149
|
t.string 'title'
|
@@ -207,7 +208,7 @@ describe PgSearch do
|
|
207
208
|
end
|
208
209
|
end
|
209
210
|
|
210
|
-
context "on the same table" do
|
211
|
+
context "when on the same table" do
|
211
212
|
with_model :DoublyAssociatedModel do
|
212
213
|
table do |t|
|
213
214
|
t.string 'title'
|
@@ -268,7 +269,7 @@ describe PgSearch do
|
|
268
269
|
end
|
269
270
|
end
|
270
271
|
|
271
|
-
context "against multiple attributes on one association" do
|
272
|
+
context "when against multiple attributes on one association" do
|
272
273
|
with_model :AssociatedModel do
|
273
274
|
table do |t|
|
274
275
|
t.string 'title'
|
@@ -289,7 +290,7 @@ describe PgSearch do
|
|
289
290
|
end
|
290
291
|
end
|
291
292
|
|
292
|
-
it "
|
293
|
+
it "joins only once" do
|
293
294
|
included = [
|
294
295
|
ModelWithAssociation.create!(
|
295
296
|
another_model: AssociatedModel.create!(
|
@@ -321,7 +322,7 @@ describe PgSearch do
|
|
321
322
|
end
|
322
323
|
end
|
323
324
|
|
324
|
-
context "against non-text columns" do
|
325
|
+
context "when against non-text columns" do
|
325
326
|
with_model :AssociatedModel do
|
326
327
|
table do |t|
|
327
328
|
t.integer 'number'
|
@@ -342,7 +343,7 @@ describe PgSearch do
|
|
342
343
|
end
|
343
344
|
end
|
344
345
|
|
345
|
-
it "
|
346
|
+
it "casts the columns to text" do
|
346
347
|
associated = AssociatedModel.create!(number: 123)
|
347
348
|
included = [
|
348
349
|
Model.create!(number: 123, another_model: associated),
|
@@ -395,7 +396,7 @@ describe PgSearch do
|
|
395
396
|
end
|
396
397
|
end
|
397
398
|
|
398
|
-
context "merging a pg_search_scope into another model's scope" do
|
399
|
+
context "when merging a pg_search_scope into another model's scope" do
|
399
400
|
with_model :ModelWithAssociation do
|
400
401
|
model do
|
401
402
|
has_many :associated_models
|
@@ -416,7 +417,7 @@ describe PgSearch do
|
|
416
417
|
end
|
417
418
|
end
|
418
419
|
|
419
|
-
it "
|
420
|
+
it "finds records of the other model" do
|
420
421
|
included_associated_1 = AssociatedModel.create(content: "foo bar")
|
421
422
|
included_associated_2 = AssociatedModel.create(content: "foo baz")
|
422
423
|
excluded_associated_1 = AssociatedModel.create(content: "baz quux")
|
@@ -441,7 +442,7 @@ describe PgSearch do
|
|
441
442
|
end
|
442
443
|
end
|
443
444
|
|
444
|
-
context "chained onto a has_many association" do
|
445
|
+
context "when chained onto a has_many association" do
|
445
446
|
with_model :Company do
|
446
447
|
model do
|
447
448
|
has_many :positions
|
@@ -461,48 +462,7 @@ describe PgSearch do
|
|
461
462
|
end
|
462
463
|
|
463
464
|
# https://github.com/Casecommons/pg_search/issues/106
|
464
|
-
it "
|
465
|
-
company = Company.create!
|
466
|
-
another_company = Company.create!
|
467
|
-
|
468
|
-
included = [
|
469
|
-
Position.create!(company_id: company.id, title: "teller 1")
|
470
|
-
]
|
471
|
-
|
472
|
-
excluded = [
|
473
|
-
Position.create!(company_id: nil, title: "teller 1"),
|
474
|
-
Position.create!(company_id: another_company.id, title: "teller 1"),
|
475
|
-
Position.create!(company_id: company.id, title: "penn 1")
|
476
|
-
]
|
477
|
-
|
478
|
-
results = company.positions.search('teller 1')
|
479
|
-
|
480
|
-
expect(results).to include(*included)
|
481
|
-
expect(results).not_to include(*excluded)
|
482
|
-
end
|
483
|
-
end
|
484
|
-
|
485
|
-
context "chained onto a has_many association" do
|
486
|
-
with_model :Company do
|
487
|
-
model do
|
488
|
-
has_many :positions
|
489
|
-
end
|
490
|
-
end
|
491
|
-
|
492
|
-
with_model :Position do
|
493
|
-
table do |t|
|
494
|
-
t.string :title
|
495
|
-
t.belongs_to :company
|
496
|
-
end
|
497
|
-
|
498
|
-
model do
|
499
|
-
include PgSearch::Model
|
500
|
-
pg_search_scope :search, against: :title, using: %i[tsearch trigram]
|
501
|
-
end
|
502
|
-
end
|
503
|
-
|
504
|
-
# https://github.com/Casecommons/pg_search/issues/106
|
505
|
-
it "should handle numbers in a trigram query properly" do
|
465
|
+
it "handles numbers in a trigram query properly" do
|
506
466
|
company = Company.create!
|
507
467
|
another_company = Company.create!
|
508
468
|
|
@@ -524,3 +484,4 @@ describe PgSearch do
|
|
524
484
|
end
|
525
485
|
end
|
526
486
|
end
|
487
|
+
# rubocop:enable RSpec/NestedGroups
|