pg_search 2.3.2 → 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/dependabot.yml +11 -0
- data/.github/workflows/ci.yml +80 -0
- data/.jrubyrc +1 -0
- data/.standard.yml +6 -0
- data/CHANGELOG.md +55 -20
- data/CODE_OF_CONDUCT.md +76 -0
- data/Gemfile +19 -6
- data/LICENSE +1 -1
- data/README.md +106 -43
- 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 +9 -9
- data/lib/pg_search/features/dmetaphone.rb +5 -7
- 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 +26 -24
- 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/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 +6 -6
- data/lib/pg_search/multisearch/rebuilder.rb +2 -2
- data/lib/pg_search/multisearch.rb +23 -4
- data/lib/pg_search/multisearchable.rb +7 -7
- data/lib/pg_search/normalizer.rb +5 -5
- data/lib/pg_search/scope_options.rb +31 -13
- data/lib/pg_search/tasks.rb +3 -3
- data/lib/pg_search/version.rb +1 -1
- data/lib/pg_search.rb +5 -5
- data/pg_search.gemspec +16 -24
- data/spec/.rubocop.yml +20 -7
- data/spec/integration/.rubocop.yml +11 -0
- data/spec/integration/associations_spec.rb +121 -160
- data/spec/integration/deprecation_spec.rb +7 -8
- data/spec/integration/pg_search_spec.rb +390 -332
- data/spec/integration/single_table_inheritance_spec.rb +5 -5
- data/spec/lib/pg_search/configuration/association_spec.rb +21 -19
- 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 +32 -28
- data/spec/lib/pg_search/features/tsearch_spec.rb +57 -33
- data/spec/lib/pg_search/multisearch/rebuilder_spec.rb +94 -63
- data/spec/lib/pg_search/multisearch_spec.rb +57 -29
- data/spec/lib/pg_search/multisearchable_spec.rb +160 -107
- data/spec/lib/pg_search/normalizer_spec.rb +12 -10
- data/spec/lib/pg_search_spec.rb +75 -64
- data/spec/spec_helper.rb +21 -9
- data/spec/support/database.rb +10 -8
- metadata +20 -134
- data/.autotest +0 -5
- data/.codeclimate.yml +0 -17
- data/.rubocop.yml +0 -56
- data/.travis.yml +0 -50
data/Rakefile
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
3
|
+
require "bundler"
|
|
4
4
|
Bundler::GemHelper.install_tasks
|
|
5
5
|
|
|
6
|
-
require
|
|
6
|
+
require "rspec/core/rake_task"
|
|
7
7
|
RSpec::Core::RakeTask.new(:spec)
|
|
8
8
|
|
|
9
|
-
require "
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
require "standard/rake"
|
|
10
|
+
|
|
11
|
+
desc "Check test coverage"
|
|
12
|
+
task :undercover do
|
|
13
|
+
system("git fetch --unshallow") if ENV["CI"]
|
|
14
|
+
exit(1) unless system("bin/undercover --compare origin/master")
|
|
12
15
|
end
|
|
13
16
|
|
|
14
|
-
task default: %w[spec
|
|
17
|
+
task default: %w[spec standard undercover]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
3
|
+
require "digest"
|
|
4
4
|
|
|
5
5
|
module PgSearch
|
|
6
6
|
class Configuration
|
|
@@ -9,18 +9,20 @@ module PgSearch
|
|
|
9
9
|
|
|
10
10
|
def initialize(column_name, weight, model)
|
|
11
11
|
@name = column_name.to_s
|
|
12
|
-
@column_name = column_name
|
|
12
|
+
@column_name = column_name
|
|
13
13
|
@weight = weight
|
|
14
14
|
@model = model
|
|
15
15
|
@connection = model.connection
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
def full_name
|
|
19
|
+
return @column_name if @column_name.is_a?(Arel::Nodes::SqlLiteral)
|
|
20
|
+
|
|
19
21
|
"#{table_name}.#{column_name}"
|
|
20
22
|
end
|
|
21
23
|
|
|
22
24
|
def to_sql
|
|
23
|
-
"coalesce(#{expression}::text, '')"
|
|
25
|
+
"coalesce((#{expression})::text, '')"
|
|
24
26
|
end
|
|
25
27
|
|
|
26
28
|
private
|
|
@@ -30,7 +32,7 @@ module PgSearch
|
|
|
30
32
|
end
|
|
31
33
|
|
|
32
34
|
def column_name
|
|
33
|
-
@connection.quote_column_name(@
|
|
35
|
+
@connection.quote_column_name(@name)
|
|
34
36
|
end
|
|
35
37
|
|
|
36
38
|
def expression
|
|
@@ -80,7 +80,7 @@ module PgSearch
|
|
|
80
80
|
attr_reader :options
|
|
81
81
|
|
|
82
82
|
def default_options
|
|
83
|
-
{
|
|
83
|
+
{using: :tsearch}
|
|
84
84
|
end
|
|
85
85
|
|
|
86
86
|
VALID_KEYS = %w[
|
|
@@ -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,28 +1,28 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
3
|
+
require "logger"
|
|
4
4
|
|
|
5
5
|
module PgSearch
|
|
6
|
-
class Document < ActiveRecord::Base
|
|
6
|
+
class Document < ActiveRecord::Base # standard:disable Rails/ApplicationRecord
|
|
7
7
|
include PgSearch::Model
|
|
8
8
|
|
|
9
|
-
self.table_name =
|
|
9
|
+
self.table_name = "pg_search_documents"
|
|
10
10
|
belongs_to :searchable, polymorphic: true
|
|
11
11
|
|
|
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|
|
|
19
19
|
options = if PgSearch.multisearch_options.respond_to?(:call)
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
PgSearch.multisearch_options.call(*args)
|
|
21
|
+
else
|
|
22
|
+
{query: args.first}.merge(PgSearch.multisearch_options)
|
|
23
|
+
end
|
|
24
24
|
|
|
25
|
-
{
|
|
25
|
+
{against: :content}.merge(options)
|
|
26
26
|
}
|
|
27
27
|
end
|
|
28
28
|
end
|
|
@@ -1,21 +1,19 @@
|
|
|
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
|
|
6
8
|
def initialize(query, options, columns, model, normalizer)
|
|
7
9
|
dmetaphone_normalizer = Normalizer.new(normalizer)
|
|
8
|
-
options = (options || {}).merge(dictionary:
|
|
10
|
+
options = (options || {}).merge(dictionary: "simple")
|
|
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
|
|
|
@@ -35,17 +35,17 @@ module PgSearch
|
|
|
35
35
|
|
|
36
36
|
def similarity_function
|
|
37
37
|
if word_similarity?
|
|
38
|
-
|
|
38
|
+
"word_similarity"
|
|
39
39
|
else
|
|
40
|
-
|
|
40
|
+
"similarity"
|
|
41
41
|
end
|
|
42
42
|
end
|
|
43
43
|
|
|
44
44
|
def infix_operator
|
|
45
45
|
if word_similarity?
|
|
46
|
-
|
|
46
|
+
"<%"
|
|
47
47
|
else
|
|
48
|
-
|
|
48
|
+
"%"
|
|
49
49
|
end
|
|
50
50
|
end
|
|
51
51
|
|
|
@@ -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
|
|
|
@@ -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
|
-
def tsquery_for_term(unsanitized_term)
|
|
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
|
|
|
@@ -107,33 +108,34 @@ module PgSearch
|
|
|
107
108
|
|
|
108
109
|
term_sql = Arel.sql(normalize(connection.quote(sanitized_term)))
|
|
109
110
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
111
|
+
tsquery = tsquery_expression(term_sql, negated: negated, prefix: options[:prefix])
|
|
112
|
+
|
|
113
|
+
Arel::Nodes::NamedFunction.new("to_tsquery", [dictionary, tsquery]).to_sql
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# After this, the SQL expression evaluates to a string containing the term surrounded by single-quotes.
|
|
117
|
+
# If :prefix is true, then the term will have :* appended to the end.
|
|
118
|
+
# If :negated is true, then the term will have ! prepended to the front.
|
|
119
|
+
def tsquery_expression(term_sql, negated:, prefix:)
|
|
113
120
|
terms = [
|
|
114
|
-
(Arel::Nodes.build_quoted(
|
|
121
|
+
(Arel::Nodes.build_quoted("!") if negated),
|
|
115
122
|
Arel::Nodes.build_quoted("' "),
|
|
116
123
|
term_sql,
|
|
117
124
|
Arel::Nodes.build_quoted(" '"),
|
|
118
|
-
(Arel::Nodes.build_quoted(":*") if
|
|
125
|
+
(Arel::Nodes.build_quoted(":*") if prefix)
|
|
119
126
|
].compact
|
|
120
127
|
|
|
121
|
-
|
|
128
|
+
terms.inject do |memo, term|
|
|
122
129
|
Arel::Nodes::InfixOperation.new("||", memo, Arel::Nodes.build_quoted(term))
|
|
123
130
|
end
|
|
124
|
-
|
|
125
|
-
Arel::Nodes::NamedFunction.new(
|
|
126
|
-
"to_tsquery",
|
|
127
|
-
[dictionary, tsquery_sql]
|
|
128
|
-
).to_sql
|
|
129
131
|
end
|
|
130
132
|
|
|
131
133
|
def tsquery
|
|
132
134
|
return "''" if query.blank?
|
|
133
135
|
|
|
134
|
-
query_terms = query.split
|
|
136
|
+
query_terms = query.split.compact
|
|
135
137
|
tsquery_terms = query_terms.map { |term| tsquery_for_term(term) }
|
|
136
|
-
tsquery_terms.join(options[:any_word] ?
|
|
138
|
+
tsquery_terms.join(options[:any_word] ? " || " : " && ")
|
|
137
139
|
end
|
|
138
140
|
|
|
139
141
|
def tsdocument
|
|
@@ -151,7 +153,7 @@ module PgSearch
|
|
|
151
153
|
end
|
|
152
154
|
end
|
|
153
155
|
|
|
154
|
-
tsdocument_terms.join(
|
|
156
|
+
tsdocument_terms.join(" || ")
|
|
155
157
|
end
|
|
156
158
|
|
|
157
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
|
|
@@ -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
|
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)
|
|
@@ -30,7 +30,7 @@ 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
34
|
end
|
|
35
35
|
|
|
36
36
|
def additional_attributes?
|
|
@@ -46,7 +46,7 @@ module PgSearch
|
|
|
46
46
|
end
|
|
47
47
|
|
|
48
48
|
def rebuild_sql_template
|
|
49
|
-
|
|
49
|
+
<<~SQL.squish
|
|
50
50
|
INSERT INTO :documents_table (searchable_type, searchable_id, content, created_at, updated_at)
|
|
51
51
|
SELECT :base_model_name AS searchable_type,
|
|
52
52
|
:model_table.#{primary_key} AS searchable_id,
|
|
@@ -5,16 +5,35 @@ 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
|
+
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
|
+
category: :deprecated,
|
|
14
|
+
uplevel: 1
|
|
15
|
+
)
|
|
16
|
+
clean_up = deprecated_clean_up
|
|
12
17
|
end
|
|
18
|
+
|
|
19
|
+
if transactional
|
|
20
|
+
model.transaction { execute(model, clean_up) }
|
|
21
|
+
else
|
|
22
|
+
execute(model, clean_up)
|
|
23
|
+
end
|
|
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
|
|
13
31
|
end
|
|
14
32
|
end
|
|
15
33
|
|
|
16
34
|
class ModelNotMultisearchable < StandardError
|
|
17
35
|
def initialize(model_class)
|
|
36
|
+
super
|
|
18
37
|
@model_class = model_class
|
|
19
38
|
end
|
|
20
39
|
|
|
@@ -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,
|
|
@@ -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
|
|
@@ -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
|
|
|
@@ -152,7 +170,7 @@ module PgSearch
|
|
|
152
170
|
return scope if scope.included_modules.include?(PgSearchRankTableAliasing)
|
|
153
171
|
|
|
154
172
|
scope.all.spawn.tap do |new_scope|
|
|
155
|
-
new_scope.
|
|
173
|
+
new_scope.instance_eval { extend PgSearchRankTableAliasing }
|
|
156
174
|
end
|
|
157
175
|
end
|
|
158
176
|
end
|