pg_search 2.2.0 → 2.3.4
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/.editorconfig +10 -0
- data/.github/dependabot.yml +11 -0
- data/.rubocop.yml +88 -7
- data/.travis.yml +11 -19
- data/CHANGELOG.md +40 -15
- data/Gemfile +1 -1
- data/LICENSE +1 -1
- data/README.md +46 -45
- data/lib/pg_search.rb +13 -56
- data/lib/pg_search/document.rb +2 -2
- 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/model.rb +57 -0
- data/lib/pg_search/multisearch.rb +10 -1
- data/lib/pg_search/multisearch/rebuilder.rb +7 -3
- data/lib/pg_search/scope_options.rb +2 -2
- data/lib/pg_search/tasks.rb +2 -1
- data/lib/pg_search/version.rb +1 -1
- data/pg_search.gemspec +9 -5
- data/spec/.rubocop.yml +2 -2
- data/spec/integration/.rubocop.yml +11 -0
- data/spec/integration/associations_spec.rb +27 -66
- data/spec/integration/deprecation_spec.rb +33 -0
- data/spec/integration/pagination_spec.rb +1 -1
- data/spec/integration/pg_search_spec.rb +70 -59
- data/spec/integration/single_table_inheritance_spec.rb +2 -2
- data/spec/lib/pg_search/configuration/association_spec.rb +10 -8
- data/spec/lib/pg_search/configuration/foreign_column_spec.rb +1 -1
- 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 +124 -76
- data/spec/lib/pg_search/multisearch_spec.rb +49 -30
- data/spec/lib/pg_search/multisearchable_spec.rb +155 -101
- data/spec/lib/pg_search/normalizer_spec.rb +12 -10
- data/spec/lib/pg_search_spec.rb +59 -46
- data/spec/spec_helper.rb +13 -4
- data/spec/support/database.rb +1 -1
- metadata +76 -13
data/lib/pg_search.rb
CHANGED
@@ -7,6 +7,7 @@ require "active_support/core_ext/string/strip"
|
|
7
7
|
|
8
8
|
require "pg_search/configuration"
|
9
9
|
require "pg_search/features"
|
10
|
+
require "pg_search/model"
|
10
11
|
require "pg_search/multisearch"
|
11
12
|
require "pg_search/multisearchable"
|
12
13
|
require "pg_search/normalizer"
|
@@ -14,7 +15,17 @@ require "pg_search/scope_options"
|
|
14
15
|
require "pg_search/version"
|
15
16
|
|
16
17
|
module PgSearch
|
17
|
-
|
18
|
+
autoload :Document, "pg_search/document"
|
19
|
+
|
20
|
+
def self.included(base)
|
21
|
+
ActiveSupport::Deprecation.warn <<~MESSAGE
|
22
|
+
Directly including `PgSearch` into an Active Record model is deprecated and will be removed in pg_search 3.0.
|
23
|
+
|
24
|
+
Please replace `include PgSearch` with `include PgSearch::Model`.
|
25
|
+
MESSAGE
|
26
|
+
|
27
|
+
base.include PgSearch::Model
|
28
|
+
end
|
18
29
|
|
19
30
|
mattr_accessor :multisearch_options
|
20
31
|
self.multisearch_options = {}
|
@@ -22,30 +33,6 @@ module PgSearch
|
|
22
33
|
mattr_accessor :unaccent_function
|
23
34
|
self.unaccent_function = "unaccent"
|
24
35
|
|
25
|
-
module ClassMethods
|
26
|
-
def pg_search_scope(name, options)
|
27
|
-
options_proc = if options.respond_to?(:call)
|
28
|
-
options
|
29
|
-
elsif options.respond_to?(:merge)
|
30
|
-
->(query) { { query: query }.merge(options) }
|
31
|
-
else
|
32
|
-
raise ArgumentError, 'pg_search_scope expects a Hash or Proc'
|
33
|
-
end
|
34
|
-
|
35
|
-
define_singleton_method(name) do |*args|
|
36
|
-
config = Configuration.new(options_proc.call(*args), self)
|
37
|
-
scope_options = ScopeOptions.new(config)
|
38
|
-
scope_options.apply(self)
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
42
|
-
def multisearchable(options = {})
|
43
|
-
include PgSearch::Multisearchable
|
44
|
-
class_attribute :pg_search_multisearchable_options
|
45
|
-
self.pg_search_multisearchable_options = options
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
36
|
class << self
|
50
37
|
def multisearch(*args)
|
51
38
|
PgSearch::Document.search(*args)
|
@@ -67,32 +54,6 @@ module PgSearch
|
|
67
54
|
end
|
68
55
|
end
|
69
56
|
|
70
|
-
def method_missing(symbol, *args)
|
71
|
-
case symbol
|
72
|
-
when :pg_search_rank
|
73
|
-
raise PgSearchRankNotSelected unless respond_to?(:pg_search_rank)
|
74
|
-
|
75
|
-
read_attribute(:pg_search_rank).to_f
|
76
|
-
when :pg_search_highlight
|
77
|
-
raise PgSearchHighlightNotSelected unless respond_to?(:pg_search_highlight)
|
78
|
-
|
79
|
-
read_attribute(:pg_search_highlight)
|
80
|
-
else
|
81
|
-
super
|
82
|
-
end
|
83
|
-
end
|
84
|
-
|
85
|
-
def respond_to_missing?(symbol, *args)
|
86
|
-
case symbol
|
87
|
-
when :pg_search_rank
|
88
|
-
attributes.key?(:pg_search_rank)
|
89
|
-
when :pg_search_highlight
|
90
|
-
attributes.key?(:pg_search_highlight)
|
91
|
-
else
|
92
|
-
super
|
93
|
-
end
|
94
|
-
end
|
95
|
-
|
96
57
|
class PgSearchRankNotSelected < StandardError
|
97
58
|
def message
|
98
59
|
"You must chain .with_pg_search_rank after the pg_search_scope " \
|
@@ -108,8 +69,4 @@ module PgSearch
|
|
108
69
|
end
|
109
70
|
end
|
110
71
|
|
111
|
-
|
112
|
-
require "pg_search/document"
|
113
|
-
end
|
114
|
-
|
115
|
-
require "pg_search/railtie" if defined?(Rails)
|
72
|
+
require "pg_search/railtie" if defined?(Rails::Railtie)
|
data/lib/pg_search/document.rb
CHANGED
@@ -4,7 +4,7 @@ require 'logger'
|
|
4
4
|
|
5
5
|
module PgSearch
|
6
6
|
class Document < ActiveRecord::Base
|
7
|
-
include PgSearch
|
7
|
+
include PgSearch::Model
|
8
8
|
|
9
9
|
self.table_name = 'pg_search_documents'
|
10
10
|
belongs_to :searchable, polymorphic: true
|
@@ -12,7 +12,7 @@ module PgSearch
|
|
12
12
|
# The logger might not have loaded yet.
|
13
13
|
# https://github.com/Casecommons/pg_search/issues/26
|
14
14
|
def self.logger
|
15
|
-
super || Logger.new(
|
15
|
+
super || Logger.new($stderr)
|
16
16
|
end
|
17
17
|
|
18
18
|
pg_search_scope :search, lambda { |*args|
|
@@ -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
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PgSearch
|
4
|
+
module Model
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
def pg_search_scope(name, options)
|
9
|
+
options_proc = if options.respond_to?(:call)
|
10
|
+
options
|
11
|
+
elsif options.respond_to?(:merge)
|
12
|
+
->(query) { { query: query }.merge(options) }
|
13
|
+
else
|
14
|
+
raise ArgumentError, 'pg_search_scope expects a Hash or Proc'
|
15
|
+
end
|
16
|
+
|
17
|
+
define_singleton_method(name) do |*args|
|
18
|
+
config = Configuration.new(options_proc.call(*args), self)
|
19
|
+
scope_options = ScopeOptions.new(config)
|
20
|
+
scope_options.apply(self)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def multisearchable(options = {})
|
25
|
+
include PgSearch::Multisearchable
|
26
|
+
class_attribute :pg_search_multisearchable_options
|
27
|
+
self.pg_search_multisearchable_options = options
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def method_missing(symbol, *args)
|
32
|
+
case symbol
|
33
|
+
when :pg_search_rank
|
34
|
+
raise PgSearchRankNotSelected unless respond_to?(:pg_search_rank)
|
35
|
+
|
36
|
+
read_attribute(:pg_search_rank).to_f
|
37
|
+
when :pg_search_highlight
|
38
|
+
raise PgSearchHighlightNotSelected unless respond_to?(:pg_search_highlight)
|
39
|
+
|
40
|
+
read_attribute(:pg_search_highlight)
|
41
|
+
else
|
42
|
+
super
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def respond_to_missing?(symbol, *args)
|
47
|
+
case symbol
|
48
|
+
when :pg_search_rank
|
49
|
+
attributes.key?(:pg_search_rank)
|
50
|
+
when :pg_search_highlight
|
51
|
+
attributes.key?(:pg_search_highlight)
|
52
|
+
else
|
53
|
+
super
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
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
|
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
26
|
s.add_development_dependency 'rspec', '>= 3.3'
|
27
|
-
s.add_development_dependency 'rubocop', '>= 0.
|
27
|
+
s.add_development_dependency 'rubocop', '>= 0.90.0'
|
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'
|
33
|
+
s.add_development_dependency 'warning'
|
30
34
|
s.add_development_dependency 'with_model', '>= 1.2'
|
31
35
|
|
32
|
-
s.required_ruby_version = '>= 2.
|
36
|
+
s.required_ruby_version = '>= 2.5'
|
33
37
|
end
|
data/spec/.rubocop.yml
CHANGED