pg_search 2.3.5 → 2.3.7
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/.github/workflows/ci.yml +80 -0
- data/.standard.yml +6 -0
- data/CHANGELOG.md +20 -0
- data/Gemfile +19 -6
- data/LICENSE +1 -1
- data/README.md +61 -39
- data/Rakefile +9 -6
- data/lib/pg_search/configuration/column.rb +6 -4
- data/lib/pg_search/configuration/foreign_column.rb +1 -1
- data/lib/pg_search/configuration.rb +13 -3
- data/lib/pg_search/document.rb +8 -8
- data/lib/pg_search/features/dmetaphone.rb +1 -1
- data/lib/pg_search/features/feature.rb +1 -1
- data/lib/pg_search/features/trigram.rb +4 -4
- data/lib/pg_search/features/tsearch.rb +15 -14
- data/lib/pg_search/migration/dmetaphone_generator.rb +2 -2
- data/lib/pg_search/migration/generator.rb +5 -5
- data/lib/pg_search/migration/multisearch_generator.rb +2 -2
- data/lib/pg_search/model.rb +6 -6
- data/lib/pg_search/multisearch.rb +16 -6
- data/lib/pg_search/multisearchable.rb +7 -7
- data/lib/pg_search/normalizer.rb +5 -5
- data/lib/pg_search/scope_options.rb +28 -10
- data/lib/pg_search/tasks.rb +2 -2
- data/lib/pg_search/version.rb +1 -1
- data/lib/pg_search.rb +5 -5
- data/pg_search.gemspec +17 -29
- data/spec/.rubocop.yml +20 -7
- data/spec/integration/.rubocop.yml +2 -2
- data/spec/integration/associations_spec.rb +106 -106
- data/spec/integration/deprecation_spec.rb +7 -8
- data/spec/integration/pg_search_spec.rb +339 -292
- data/spec/integration/single_table_inheritance_spec.rb +5 -5
- data/spec/lib/pg_search/configuration/association_spec.rb +15 -15
- data/spec/lib/pg_search/configuration/column_spec.rb +13 -1
- data/spec/lib/pg_search/configuration/foreign_column_spec.rb +4 -4
- data/spec/lib/pg_search/features/dmetaphone_spec.rb +4 -4
- data/spec/lib/pg_search/features/trigram_spec.rb +28 -28
- data/spec/lib/pg_search/features/tsearch_spec.rb +57 -39
- data/spec/lib/pg_search/multisearch/rebuilder_spec.rb +17 -17
- data/spec/lib/pg_search/multisearch_spec.rb +15 -6
- data/spec/lib/pg_search/multisearchable_spec.rb +26 -26
- data/spec/lib/pg_search/normalizer_spec.rb +7 -7
- data/spec/lib/pg_search_spec.rb +20 -20
- data/spec/spec_helper.rb +15 -9
- data/spec/support/database.rb +9 -7
- metadata +13 -188
- data/.autotest +0 -5
- data/.codeclimate.yml +0 -17
- data/.rubocop.yml +0 -134
- data/.travis.yml +0 -42
@@ -1,11 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "active_support/core_ext/module/delegation"
|
4
|
-
require
|
4
|
+
require "active_support/deprecation"
|
5
5
|
|
6
6
|
module PgSearch
|
7
7
|
module Features
|
8
|
-
class TSearch < Feature
|
8
|
+
class TSearch < Feature
|
9
9
|
def self.valid_options
|
10
10
|
super + %i[dictionary prefix negation any_word normalization tsvector_column highlight]
|
11
11
|
end
|
@@ -36,12 +36,11 @@ module PgSearch
|
|
36
36
|
end
|
37
37
|
|
38
38
|
def ts_headline_options
|
39
|
-
return
|
39
|
+
return "" unless options[:highlight].is_a?(Hash)
|
40
40
|
|
41
41
|
headline_options
|
42
42
|
.merge(deprecated_headline_options)
|
43
|
-
.
|
44
|
-
.compact
|
43
|
+
.filter_map { |key, value| "#{key} = #{value}" unless value.nil? }
|
45
44
|
.join(", ")
|
46
45
|
end
|
47
46
|
|
@@ -59,7 +58,7 @@ module PgSearch
|
|
59
58
|
end
|
60
59
|
end
|
61
60
|
|
62
|
-
def deprecated_headline_options
|
61
|
+
def deprecated_headline_options
|
63
62
|
indifferent_options = options.with_indifferent_access
|
64
63
|
|
65
64
|
%w[
|
@@ -71,9 +70,11 @@ module PgSearch
|
|
71
70
|
unless value.nil?
|
72
71
|
key = deprecated_key.camelize
|
73
72
|
|
74
|
-
|
73
|
+
warn(
|
75
74
|
"pg_search 3.0 will no longer accept :#{deprecated_key} as an argument to :ts_headline, " \
|
76
|
-
"use :#{key} instead."
|
75
|
+
"use :#{key} instead.",
|
76
|
+
category: :deprecated,
|
77
|
+
uplevel: 1
|
77
78
|
)
|
78
79
|
|
79
80
|
hash[key] = ts_headline_option_value(value)
|
@@ -95,11 +96,11 @@ module PgSearch
|
|
95
96
|
end
|
96
97
|
end
|
97
98
|
|
98
|
-
DISALLOWED_TSQUERY_CHARACTERS = /['
|
99
|
+
DISALLOWED_TSQUERY_CHARACTERS = /['?\\:‘’ʻʼ]/
|
99
100
|
|
100
101
|
def tsquery_for_term(unsanitized_term)
|
101
102
|
if options[:negation] && unsanitized_term.start_with?("!")
|
102
|
-
unsanitized_term[0] =
|
103
|
+
unsanitized_term[0] = ""
|
103
104
|
negated = true
|
104
105
|
end
|
105
106
|
|
@@ -117,7 +118,7 @@ module PgSearch
|
|
117
118
|
# If :negated is true, then the term will have ! prepended to the front.
|
118
119
|
def tsquery_expression(term_sql, negated:, prefix:)
|
119
120
|
terms = [
|
120
|
-
(Arel::Nodes.build_quoted(
|
121
|
+
(Arel::Nodes.build_quoted("!") if negated),
|
121
122
|
Arel::Nodes.build_quoted("' "),
|
122
123
|
term_sql,
|
123
124
|
Arel::Nodes.build_quoted(" '"),
|
@@ -132,9 +133,9 @@ module PgSearch
|
|
132
133
|
def tsquery
|
133
134
|
return "''" if query.blank?
|
134
135
|
|
135
|
-
query_terms = query.split
|
136
|
+
query_terms = query.split.compact
|
136
137
|
tsquery_terms = query_terms.map { |term| tsquery_for_term(term) }
|
137
|
-
tsquery_terms.join(options[:any_word] ?
|
138
|
+
tsquery_terms.join(options[:any_word] ? " || " : " && ")
|
138
139
|
end
|
139
140
|
|
140
141
|
def tsdocument
|
@@ -152,7 +153,7 @@ module PgSearch
|
|
152
153
|
end
|
153
154
|
end
|
154
155
|
|
155
|
-
tsdocument_terms.join(
|
156
|
+
tsdocument_terms.join(" || ")
|
156
157
|
end
|
157
158
|
|
158
159
|
# From http://www.postgresql.org/docs/8.3/static/textsearch-controls.html
|
@@ -1,12 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "pg_search/migration/generator"
|
4
4
|
|
5
5
|
module PgSearch
|
6
6
|
module Migration
|
7
7
|
class DmetaphoneGenerator < Generator
|
8
8
|
def migration_name
|
9
|
-
|
9
|
+
"add_pg_search_dmetaphone_support_functions"
|
10
10
|
end
|
11
11
|
end
|
12
12
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
3
|
+
require "active_record"
|
4
|
+
require "rails/generators/base"
|
5
5
|
|
6
6
|
module PgSearch
|
7
7
|
module Migration
|
@@ -10,19 +10,19 @@ module PgSearch
|
|
10
10
|
|
11
11
|
def self.inherited(subclass)
|
12
12
|
super
|
13
|
-
subclass.source_root File.expand_path(
|
13
|
+
subclass.source_root File.expand_path("templates", __dir__)
|
14
14
|
end
|
15
15
|
|
16
16
|
def create_migration
|
17
17
|
now = Time.now.utc
|
18
|
-
filename = "#{now.strftime(
|
18
|
+
filename = "#{now.strftime("%Y%m%d%H%M%S")}_#{migration_name}.rb"
|
19
19
|
template "#{migration_name}.rb.erb", "db/migrate/#{filename}", migration_version
|
20
20
|
end
|
21
21
|
|
22
22
|
private
|
23
23
|
|
24
24
|
def read_sql_file(filename)
|
25
|
-
sql_directory = File.expand_path(
|
25
|
+
sql_directory = File.expand_path("../../../sql", __dir__)
|
26
26
|
source_path = File.join(sql_directory, "#{filename}.sql")
|
27
27
|
File.read(source_path).strip
|
28
28
|
end
|
@@ -1,12 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "pg_search/migration/generator"
|
4
4
|
|
5
5
|
module PgSearch
|
6
6
|
module Migration
|
7
7
|
class MultisearchGenerator < Generator
|
8
8
|
def migration_name
|
9
|
-
|
9
|
+
"create_pg_search_documents"
|
10
10
|
end
|
11
11
|
end
|
12
12
|
end
|
data/lib/pg_search/model.rb
CHANGED
@@ -7,12 +7,12 @@ module PgSearch
|
|
7
7
|
module ClassMethods
|
8
8
|
def pg_search_scope(name, options)
|
9
9
|
options_proc = if options.respond_to?(:call)
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
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
16
|
|
17
17
|
define_singleton_method(name) do |*args|
|
18
18
|
config = Configuration.new(options_proc.call(*args), self)
|
@@ -5,20 +5,30 @@ require "pg_search/multisearch/rebuilder"
|
|
5
5
|
module PgSearch
|
6
6
|
module Multisearch
|
7
7
|
class << self
|
8
|
-
def rebuild(model, deprecated_clean_up = nil, clean_up: true)
|
8
|
+
def rebuild(model, deprecated_clean_up = nil, clean_up: true, transactional: true)
|
9
9
|
unless deprecated_clean_up.nil?
|
10
|
-
|
10
|
+
warn(
|
11
11
|
"pg_search 3.0 will no longer accept a boolean second argument to PgSearchMultisearch.rebuild, " \
|
12
|
-
"use keyword argument `clean_up:` instead."
|
12
|
+
"use keyword argument `clean_up:` instead.",
|
13
|
+
category: :deprecated,
|
14
|
+
uplevel: 1
|
13
15
|
)
|
14
16
|
clean_up = deprecated_clean_up
|
15
17
|
end
|
16
18
|
|
17
|
-
|
18
|
-
|
19
|
-
|
19
|
+
if transactional
|
20
|
+
model.transaction { execute(model, clean_up) }
|
21
|
+
else
|
22
|
+
execute(model, clean_up)
|
20
23
|
end
|
21
24
|
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def execute(model, clean_up)
|
29
|
+
PgSearch::Document.where(searchable_type: model.base_class.name).delete_all if clean_up
|
30
|
+
Rebuilder.new(model).rebuild
|
31
|
+
end
|
22
32
|
end
|
23
33
|
|
24
34
|
class ModelNotMultisearchable < StandardError
|
@@ -7,12 +7,12 @@ module PgSearch
|
|
7
7
|
def self.included(mod)
|
8
8
|
mod.class_eval do
|
9
9
|
has_one :pg_search_document,
|
10
|
-
|
11
|
-
|
12
|
-
|
10
|
+
as: :searchable,
|
11
|
+
class_name: "PgSearch::Document",
|
12
|
+
dependent: :delete
|
13
13
|
|
14
14
|
after_save :update_pg_search_document,
|
15
|
-
|
15
|
+
if: -> { PgSearch.multisearch_enabled? }
|
16
16
|
end
|
17
17
|
end
|
18
18
|
|
@@ -39,7 +39,7 @@ module PgSearch
|
|
39
39
|
conditions.all? { |condition| condition.to_proc.call(self) }
|
40
40
|
end
|
41
41
|
|
42
|
-
def update_pg_search_document
|
42
|
+
def update_pg_search_document
|
43
43
|
if_conditions = Array(pg_search_multisearchable_options[:if])
|
44
44
|
unless_conditions = Array(pg_search_multisearchable_options[:unless])
|
45
45
|
|
@@ -50,7 +50,7 @@ module PgSearch
|
|
50
50
|
if should_have_document
|
51
51
|
create_or_update_pg_search_document
|
52
52
|
else
|
53
|
-
pg_search_document&.destroy
|
53
|
+
pg_search_document&.destroy # standard:disable Rails/SaveBang
|
54
54
|
end
|
55
55
|
end
|
56
56
|
|
@@ -58,7 +58,7 @@ module PgSearch
|
|
58
58
|
if !pg_search_document
|
59
59
|
create_pg_search_document(pg_search_document_attrs)
|
60
60
|
elsif should_update_pg_search_document?
|
61
|
-
pg_search_document.update(pg_search_document_attrs)
|
61
|
+
pg_search_document.update(pg_search_document_attrs) # standard:disable Rails/SaveBang
|
62
62
|
end
|
63
63
|
end
|
64
64
|
end
|
data/lib/pg_search/normalizer.rb
CHANGED
@@ -10,11 +10,11 @@ module PgSearch
|
|
10
10
|
return sql_expression unless config.ignore.include?(:accents)
|
11
11
|
|
12
12
|
sql_node = case sql_expression
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
13
|
+
when Arel::Nodes::Node
|
14
|
+
sql_expression
|
15
|
+
else
|
16
|
+
Arel.sql(sql_expression)
|
17
|
+
end
|
18
18
|
|
19
19
|
Arel::Nodes::NamedFunction.new(
|
20
20
|
PgSearch.unaccent_function,
|
@@ -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
|
@@ -93,11 +90,32 @@ module PgSearch
|
|
93
90
|
end
|
94
91
|
|
95
92
|
def conditions
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
93
|
+
expressions =
|
94
|
+
config.features
|
95
|
+
.reject { |_feature_name, feature_options| feature_options && feature_options[:sort_only] }
|
96
|
+
.map { |feature_name, _feature_options| feature_for(feature_name).conditions }
|
97
|
+
|
98
|
+
or_node(expressions)
|
99
|
+
end
|
100
|
+
|
101
|
+
# https://github.com/rails/rails/pull/51492
|
102
|
+
# :nocov:
|
103
|
+
# standard:disable Lint/DuplicateMethods
|
104
|
+
or_arity = Arel::Nodes::Or.instance_method(:initialize).arity
|
105
|
+
case or_arity
|
106
|
+
when 1
|
107
|
+
def or_node(expressions)
|
108
|
+
Arel::Nodes::Or.new(expressions)
|
109
|
+
end
|
110
|
+
when 2
|
111
|
+
def or_node(expressions)
|
112
|
+
expressions.inject { |accumulator, expression| Arel::Nodes::Or.new(accumulator, expression) }
|
113
|
+
end
|
114
|
+
else
|
115
|
+
raise "Unsupported arity #{or_arity} for Arel::Nodes::Or#initialize"
|
100
116
|
end
|
117
|
+
# :nocov:
|
118
|
+
# standard:enable Lint/DuplicateMethods
|
101
119
|
|
102
120
|
def order_within_rank
|
103
121
|
config.order_within_rank || "#{primary_key} ASC"
|
@@ -111,7 +129,7 @@ module PgSearch
|
|
111
129
|
if config.associations.any?
|
112
130
|
config.associations.map do |association|
|
113
131
|
association.join(primary_key)
|
114
|
-
end.join(
|
132
|
+
end.join(" ")
|
115
133
|
end
|
116
134
|
end
|
117
135
|
|
data/lib/pg_search/tasks.rb
CHANGED
data/lib/pg_search/version.rb
CHANGED
data/lib/pg_search.rb
CHANGED
@@ -18,7 +18,7 @@ module PgSearch
|
|
18
18
|
autoload :Document, "pg_search/document"
|
19
19
|
|
20
20
|
def self.included(base)
|
21
|
-
|
21
|
+
warn(<<~MESSAGE, category: :deprecated, uplevel: 1)
|
22
22
|
Directly including `PgSearch` into an Active Record model is deprecated and will be removed in pg_search 3.0.
|
23
23
|
|
24
24
|
Please replace `include PgSearch` with `include PgSearch::Model`.
|
@@ -34,8 +34,8 @@ module PgSearch
|
|
34
34
|
self.unaccent_function = "unaccent"
|
35
35
|
|
36
36
|
class << self
|
37
|
-
def multisearch(
|
38
|
-
PgSearch::Document.search(
|
37
|
+
def multisearch(...)
|
38
|
+
PgSearch::Document.search(...)
|
39
39
|
end
|
40
40
|
|
41
41
|
def disable_multisearch
|
@@ -57,14 +57,14 @@ module PgSearch
|
|
57
57
|
class PgSearchRankNotSelected < StandardError
|
58
58
|
def message
|
59
59
|
"You must chain .with_pg_search_rank after the pg_search_scope " \
|
60
|
-
|
60
|
+
"to access the pg_search_rank attribute on returned records"
|
61
61
|
end
|
62
62
|
end
|
63
63
|
|
64
64
|
class PgSearchHighlightNotSelected < StandardError
|
65
65
|
def message
|
66
66
|
"You must chain .with_pg_search_highlight after the pg_search_scope " \
|
67
|
-
|
67
|
+
"to access the pg_search_highlight attribute on returned records"
|
68
68
|
end
|
69
69
|
end
|
70
70
|
end
|
data/pg_search.gemspec
CHANGED
@@ -1,37 +1,25 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
$LOAD_PATH.push File.expand_path(
|
4
|
-
require
|
3
|
+
$LOAD_PATH.push File.expand_path("lib", __dir__)
|
4
|
+
require "pg_search/version"
|
5
5
|
|
6
|
-
Gem::Specification.new do |s|
|
7
|
-
s.name
|
8
|
-
s.version
|
9
|
-
s.platform
|
10
|
-
s.authors
|
11
|
-
s.email
|
12
|
-
s.homepage
|
13
|
-
s.summary
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = "pg_search"
|
8
|
+
s.version = PgSearch::VERSION
|
9
|
+
s.platform = Gem::Platform::RUBY
|
10
|
+
s.authors = ["Grant Hutchins", "Case Commons, LLC"]
|
11
|
+
s.email = %w[gems@nertzy.com casecommons-dev@googlegroups.com]
|
12
|
+
s.homepage = "https://github.com/Casecommons/pg_search"
|
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
|
-
s.licenses
|
15
|
+
s.licenses = ["MIT"]
|
16
|
+
s.metadata["rubygems_mfa_required"] = "true"
|
16
17
|
|
17
|
-
s.files
|
18
|
-
s.
|
19
|
-
s.require_paths = ['lib']
|
18
|
+
s.files = `git ls-files -z`.split("\x0")
|
19
|
+
s.require_paths = ["lib"]
|
20
20
|
|
21
|
-
s.add_dependency
|
22
|
-
s.add_dependency
|
21
|
+
s.add_dependency "activerecord", ">= 6.1"
|
22
|
+
s.add_dependency "activesupport", ">= 6.1"
|
23
23
|
|
24
|
-
s.
|
25
|
-
s.add_development_dependency 'rake'
|
26
|
-
s.add_development_dependency 'rspec'
|
27
|
-
s.add_development_dependency 'rubocop'
|
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'
|
32
|
-
s.add_development_dependency 'simplecov'
|
33
|
-
s.add_development_dependency 'warning'
|
34
|
-
s.add_development_dependency 'with_model'
|
35
|
-
|
36
|
-
s.required_ruby_version = '>= 2.5'
|
24
|
+
s.required_ruby_version = ">= 3.0"
|
37
25
|
end
|
data/spec/.rubocop.yml
CHANGED
@@ -1,14 +1,27 @@
|
|
1
1
|
inherit_from:
|
2
2
|
- ../.rubocop.yml
|
3
3
|
|
4
|
-
|
5
|
-
|
4
|
+
RSpec/ContextWording:
|
5
|
+
Prefixes:
|
6
|
+
- using
|
7
|
+
- via
|
8
|
+
- when
|
9
|
+
- with
|
10
|
+
- without
|
6
11
|
|
7
|
-
|
8
|
-
Enabled:
|
12
|
+
RSpec/DescribedClass:
|
13
|
+
Enabled: true
|
9
14
|
|
10
|
-
|
11
|
-
|
15
|
+
RSpec/ExampleLength:
|
16
|
+
Max: 15
|
12
17
|
|
13
|
-
|
18
|
+
RSpec/ExpectInHook:
|
14
19
|
Enabled: false
|
20
|
+
|
21
|
+
RSpec/FilePath:
|
22
|
+
CustomTransform:
|
23
|
+
TSearch: "tsearch"
|
24
|
+
DMetaphone: "dmetaphone"
|
25
|
+
|
26
|
+
RSpec/MultipleExpectations:
|
27
|
+
Max: 5
|