pg_search 2.1.2 → 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 +5 -5
- data/.codeclimate.yml +1 -0
- data/.editorconfig +10 -0
- data/.github/dependabot.yml +11 -0
- data/.github/workflows/ci.yml +75 -0
- data/.jrubyrc +1 -0
- data/.rubocop.yml +95 -10
- data/.travis.yml +26 -48
- data/CHANGELOG.md +179 -112
- data/CODE_OF_CONDUCT.md +76 -0
- data/CONTRIBUTING.md +5 -3
- data/Gemfile +6 -4
- data/LICENSE +1 -1
- data/README.md +307 -198
- data/Rakefile +7 -3
- data/lib/pg_search/configuration/association.rb +2 -0
- data/lib/pg_search/configuration/column.rb +2 -0
- data/lib/pg_search/configuration/foreign_column.rb +2 -0
- data/lib/pg_search/configuration.rb +20 -6
- data/lib/pg_search/document.rb +7 -5
- data/lib/pg_search/features/dmetaphone.rb +7 -7
- data/lib/pg_search/features/feature.rb +4 -2
- data/lib/pg_search/features/trigram.rb +31 -5
- data/lib/pg_search/features/tsearch.rb +18 -14
- data/lib/pg_search/features.rb +2 -0
- data/lib/pg_search/migration/dmetaphone_generator.rb +3 -1
- data/lib/pg_search/migration/generator.rb +4 -2
- data/lib/pg_search/migration/multisearch_generator.rb +2 -1
- 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 +3 -3
- data/lib/pg_search/model.rb +57 -0
- data/lib/pg_search/multisearch/rebuilder.rb +14 -6
- data/lib/pg_search/multisearch.rb +23 -6
- data/lib/pg_search/multisearchable.rb +10 -6
- data/lib/pg_search/normalizer.rb +2 -0
- data/lib/pg_search/railtie.rb +2 -0
- data/lib/pg_search/scope_options.rb +26 -49
- data/lib/pg_search/tasks.rb +5 -1
- data/lib/pg_search/version.rb +3 -1
- data/lib/pg_search.rb +17 -55
- data/pg_search.gemspec +19 -11
- data/spec/.rubocop.yml +2 -2
- data/spec/integration/.rubocop.yml +11 -0
- data/spec/integration/associations_spec.rb +125 -162
- data/spec/integration/deprecation_spec.rb +33 -0
- data/spec/integration/pagination_spec.rb +10 -8
- data/spec/integration/pg_search_spec.rb +359 -306
- data/spec/integration/single_table_inheritance_spec.rb +18 -17
- data/spec/lib/pg_search/configuration/association_spec.rb +17 -13
- data/spec/lib/pg_search/configuration/column_spec.rb +2 -0
- data/spec/lib/pg_search/configuration/foreign_column_spec.rb +6 -4
- data/spec/lib/pg_search/features/dmetaphone_spec.rb +6 -4
- data/spec/lib/pg_search/features/trigram_spec.rb +51 -20
- data/spec/lib/pg_search/features/tsearch_spec.rb +29 -21
- data/spec/lib/pg_search/multisearch/rebuilder_spec.rb +151 -85
- data/spec/lib/pg_search/multisearch_spec.rb +67 -37
- data/spec/lib/pg_search/multisearchable_spec.rb +217 -123
- data/spec/lib/pg_search/normalizer_spec.rb +14 -10
- data/spec/lib/pg_search_spec.rb +102 -89
- data/spec/spec_helper.rb +25 -6
- data/spec/support/database.rb +19 -21
- data/spec/support/with_model.rb +2 -0
- metadata +106 -29
- data/.autotest +0 -5
- data/.rubocop_todo.yml +0 -163
- data/Guardfile +0 -6
data/Rakefile
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'bundler'
|
2
4
|
Bundler::GemHelper.install_tasks
|
3
5
|
|
@@ -9,8 +11,10 @@ RuboCop::RakeTask.new do |t|
|
|
9
11
|
t.options = %w[--display-cop-names]
|
10
12
|
end
|
11
13
|
|
12
|
-
|
13
|
-
|
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")
|
14
18
|
end
|
15
19
|
|
16
|
-
task :
|
20
|
+
task default: %w[spec rubocop undercover]
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "pg_search/configuration/association"
|
2
4
|
require "pg_search/configuration/column"
|
3
5
|
require "pg_search/configuration/foreign_column"
|
@@ -17,7 +19,7 @@ module PgSearch
|
|
17
19
|
def alias(*strings)
|
18
20
|
name = Array(strings).compact.join("_")
|
19
21
|
# By default, PostgreSQL limits names to 32 characters, so we hash and limit to 32 characters.
|
20
|
-
"pg_search_#{Digest::SHA2.hexdigest(name)}"
|
22
|
+
"pg_search_#{Digest::SHA2.hexdigest(name)}".first(32)
|
21
23
|
end
|
22
24
|
end
|
23
25
|
|
@@ -27,6 +29,7 @@ module PgSearch
|
|
27
29
|
|
28
30
|
def regular_columns
|
29
31
|
return [] unless options[:against]
|
32
|
+
|
30
33
|
Array(options[:against]).map do |column_name, weight|
|
31
34
|
Column.new(column_name, weight, model)
|
32
35
|
end
|
@@ -34,6 +37,7 @@ module PgSearch
|
|
34
37
|
|
35
38
|
def associations
|
36
39
|
return [] unless options[:associated_against]
|
40
|
+
|
37
41
|
options[:associated_against].map do |association, column_names|
|
38
42
|
Association.new(model, association, column_names)
|
39
43
|
end.flatten
|
@@ -76,7 +80,7 @@ module PgSearch
|
|
76
80
|
attr_reader :options
|
77
81
|
|
78
82
|
def default_options
|
79
|
-
{:
|
83
|
+
{ using: :tsearch }
|
80
84
|
end
|
81
85
|
|
82
86
|
VALID_KEYS = %w[
|
@@ -84,12 +88,15 @@ module PgSearch
|
|
84
88
|
].map(&:to_sym)
|
85
89
|
|
86
90
|
VALID_VALUES = {
|
87
|
-
:
|
88
|
-
}
|
91
|
+
ignoring: [:accents]
|
92
|
+
}.freeze
|
89
93
|
|
90
94
|
def assert_valid_options(options)
|
91
|
-
unless options[:against] || options[:associated_against]
|
92
|
-
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
|
+
)
|
93
100
|
end
|
94
101
|
|
95
102
|
options.assert_valid_keys(VALID_KEYS)
|
@@ -100,5 +107,12 @@ module PgSearch
|
|
100
107
|
end
|
101
108
|
end
|
102
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
|
103
117
|
end
|
104
118
|
end
|
data/lib/pg_search/document.rb
CHANGED
@@ -1,26 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'logger'
|
2
4
|
|
3
5
|
module PgSearch
|
4
6
|
class Document < ActiveRecord::Base
|
5
|
-
include PgSearch
|
7
|
+
include PgSearch::Model
|
6
8
|
|
7
9
|
self.table_name = 'pg_search_documents'
|
8
|
-
belongs_to :searchable, :
|
10
|
+
belongs_to :searchable, polymorphic: true
|
9
11
|
|
10
12
|
# The logger might not have loaded yet.
|
11
13
|
# https://github.com/Casecommons/pg_search/issues/26
|
12
14
|
def self.logger
|
13
|
-
super || Logger.new(
|
15
|
+
super || Logger.new($stderr)
|
14
16
|
end
|
15
17
|
|
16
18
|
pg_search_scope :search, lambda { |*args|
|
17
19
|
options = if PgSearch.multisearch_options.respond_to?(:call)
|
18
20
|
PgSearch.multisearch_options.call(*args)
|
19
21
|
else
|
20
|
-
{:
|
22
|
+
{ query: args.first }.merge(PgSearch.multisearch_options)
|
21
23
|
end
|
22
24
|
|
23
|
-
{:
|
25
|
+
{ against: :content }.merge(options)
|
24
26
|
}
|
25
27
|
end
|
26
28
|
end
|
@@ -1,19 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/module/delegation"
|
4
|
+
|
1
5
|
module PgSearch
|
2
6
|
module Features
|
3
7
|
class DMetaphone
|
4
8
|
def initialize(query, options, columns, model, normalizer)
|
5
9
|
dmetaphone_normalizer = Normalizer.new(normalizer)
|
6
|
-
options = (options || {}).merge(:
|
10
|
+
options = (options || {}).merge(dictionary: 'simple')
|
7
11
|
@tsearch = TSearch.new(query, options, columns, model, dmetaphone_normalizer)
|
8
12
|
end
|
9
13
|
|
10
|
-
|
11
|
-
tsearch.conditions
|
12
|
-
end
|
14
|
+
delegate :conditions, to: :tsearch
|
13
15
|
|
14
|
-
|
15
|
-
tsearch.rank
|
16
|
-
end
|
16
|
+
delegate :rank, to: :tsearch
|
17
17
|
|
18
18
|
private
|
19
19
|
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "active_support/core_ext/module/delegation"
|
2
4
|
require "active_support/core_ext/hash/keys"
|
3
5
|
|
@@ -8,7 +10,7 @@ module PgSearch
|
|
8
10
|
%i[only sort_only]
|
9
11
|
end
|
10
12
|
|
11
|
-
delegate :connection, :quoted_table_name, :
|
13
|
+
delegate :connection, :quoted_table_name, to: :@model
|
12
14
|
|
13
15
|
def initialize(query, options, all_columns, model, normalizer)
|
14
16
|
@query = query
|
@@ -23,7 +25,7 @@ module PgSearch
|
|
23
25
|
attr_reader :query, :options, :all_columns, :model, :normalizer
|
24
26
|
|
25
27
|
def document
|
26
|
-
columns.map
|
28
|
+
columns.map(&:to_sql).join(" || ' ' || ")
|
27
29
|
end
|
28
30
|
|
29
31
|
def columns
|
@@ -1,8 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module PgSearch
|
2
4
|
module Features
|
3
5
|
class Trigram < Feature
|
4
6
|
def self.valid_options
|
5
|
-
super + [
|
7
|
+
super + %i[threshold word_similarity]
|
6
8
|
end
|
7
9
|
|
8
10
|
def conditions
|
@@ -12,7 +14,11 @@ module PgSearch
|
|
12
14
|
)
|
13
15
|
else
|
14
16
|
Arel::Nodes::Grouping.new(
|
15
|
-
Arel::Nodes::InfixOperation.new(
|
17
|
+
Arel::Nodes::InfixOperation.new(
|
18
|
+
infix_operator,
|
19
|
+
normalized_query,
|
20
|
+
normalized_document
|
21
|
+
)
|
16
22
|
)
|
17
23
|
end
|
18
24
|
end
|
@@ -23,12 +29,32 @@ module PgSearch
|
|
23
29
|
|
24
30
|
private
|
25
31
|
|
32
|
+
def word_similarity?
|
33
|
+
options[:word_similarity]
|
34
|
+
end
|
35
|
+
|
36
|
+
def similarity_function
|
37
|
+
if word_similarity?
|
38
|
+
'word_similarity'
|
39
|
+
else
|
40
|
+
'similarity'
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def infix_operator
|
45
|
+
if word_similarity?
|
46
|
+
'<%'
|
47
|
+
else
|
48
|
+
'%'
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
26
52
|
def similarity
|
27
53
|
Arel::Nodes::NamedFunction.new(
|
28
|
-
|
54
|
+
similarity_function,
|
29
55
|
[
|
30
|
-
|
31
|
-
|
56
|
+
normalized_query,
|
57
|
+
normalized_document
|
32
58
|
]
|
33
59
|
)
|
34
60
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "active_support/core_ext/module/delegation"
|
2
4
|
require 'active_support/deprecation'
|
3
5
|
|
@@ -57,7 +59,7 @@ module PgSearch
|
|
57
59
|
end
|
58
60
|
end
|
59
61
|
|
60
|
-
def deprecated_headline_options
|
62
|
+
def deprecated_headline_options # rubocop:disable Metrics/MethodLength
|
61
63
|
indifferent_options = options.with_indifferent_access
|
62
64
|
|
63
65
|
%w[
|
@@ -93,9 +95,9 @@ module PgSearch
|
|
93
95
|
end
|
94
96
|
end
|
95
97
|
|
96
|
-
DISALLOWED_TSQUERY_CHARACTERS = /['
|
98
|
+
DISALLOWED_TSQUERY_CHARACTERS = /['?\\:‘’]/.freeze
|
97
99
|
|
98
|
-
def tsquery_for_term(unsanitized_term)
|
100
|
+
def tsquery_for_term(unsanitized_term)
|
99
101
|
if options[:negation] && unsanitized_term.start_with?("!")
|
100
102
|
unsanitized_term[0] = ''
|
101
103
|
negated = true
|
@@ -105,30 +107,32 @@ module PgSearch
|
|
105
107
|
|
106
108
|
term_sql = Arel.sql(normalize(connection.quote(sanitized_term)))
|
107
109
|
|
108
|
-
|
109
|
-
|
110
|
-
|
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:)
|
111
119
|
terms = [
|
112
120
|
(Arel::Nodes.build_quoted('!') if negated),
|
113
121
|
Arel::Nodes.build_quoted("' "),
|
114
122
|
term_sql,
|
115
123
|
Arel::Nodes.build_quoted(" '"),
|
116
|
-
(Arel::Nodes.build_quoted(":*") if
|
124
|
+
(Arel::Nodes.build_quoted(":*") if prefix)
|
117
125
|
].compact
|
118
126
|
|
119
|
-
|
127
|
+
terms.inject do |memo, term|
|
120
128
|
Arel::Nodes::InfixOperation.new("||", memo, Arel::Nodes.build_quoted(term))
|
121
129
|
end
|
122
|
-
|
123
|
-
Arel::Nodes::NamedFunction.new(
|
124
|
-
"to_tsquery",
|
125
|
-
[dictionary, tsquery_sql]
|
126
|
-
).to_sql
|
127
130
|
end
|
128
131
|
|
129
132
|
def tsquery
|
130
133
|
return "''" if query.blank?
|
131
|
-
|
134
|
+
|
135
|
+
query_terms = query.split.compact
|
132
136
|
tsquery_terms = query_terms.map { |term| tsquery_for_term(term) }
|
133
137
|
tsquery_terms.join(options[:any_word] ? ' || ' : ' && ')
|
134
138
|
end
|
data/lib/pg_search/features.rb
CHANGED
@@ -1,10 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'pg_search/migration/generator'
|
2
4
|
|
3
5
|
module PgSearch
|
4
6
|
module Migration
|
5
7
|
class DmetaphoneGenerator < Generator
|
6
8
|
def migration_name
|
7
|
-
'add_pg_search_dmetaphone_support_functions'
|
9
|
+
'add_pg_search_dmetaphone_support_functions'
|
8
10
|
end
|
9
11
|
end
|
10
12
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'active_record'
|
2
4
|
require 'rails/generators/base'
|
3
5
|
|
@@ -8,7 +10,7 @@ module PgSearch
|
|
8
10
|
|
9
11
|
def self.inherited(subclass)
|
10
12
|
super
|
11
|
-
subclass.source_root File.expand_path('
|
13
|
+
subclass.source_root File.expand_path('templates', __dir__)
|
12
14
|
end
|
13
15
|
|
14
16
|
def create_migration
|
@@ -20,7 +22,7 @@ module PgSearch
|
|
20
22
|
private
|
21
23
|
|
22
24
|
def read_sql_file(filename)
|
23
|
-
sql_directory = File.expand_path('
|
25
|
+
sql_directory = File.expand_path('../../../sql', __dir__)
|
24
26
|
source_path = File.join(sql_directory, "#{filename}.sql")
|
25
27
|
File.read(source_path).strip
|
26
28
|
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,15 +1,15 @@
|
|
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
|
6
|
-
t.belongs_to :searchable, :
|
6
|
+
t.belongs_to :searchable, polymorphic: true, index: true
|
7
7
|
t.timestamps null: false
|
8
8
|
end
|
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
|
@@ -1,8 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module PgSearch
|
2
4
|
module Multisearch
|
3
5
|
class Rebuilder
|
4
6
|
def initialize(model, time_source = Time.method(:now))
|
5
|
-
raise ModelNotMultisearchable
|
7
|
+
raise ModelNotMultisearchable, model unless model.respond_to?(:pg_search_multisearchable_options)
|
6
8
|
|
7
9
|
@model = model
|
8
10
|
@time_source = time_source
|
@@ -11,8 +13,8 @@ module PgSearch
|
|
11
13
|
def rebuild
|
12
14
|
if model.respond_to?(:rebuild_pg_search_documents)
|
13
15
|
model.rebuild_pg_search_documents
|
14
|
-
elsif conditional? || dynamic?
|
15
|
-
model.find_each
|
16
|
+
elsif conditional? || dynamic? || additional_attributes?
|
17
|
+
model.find_each(&:update_pg_search_document)
|
16
18
|
else
|
17
19
|
model.connection.execute(rebuild_sql)
|
18
20
|
end
|
@@ -28,7 +30,11 @@ module PgSearch
|
|
28
30
|
|
29
31
|
def dynamic?
|
30
32
|
column_names = model.columns.map(&:name)
|
31
|
-
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)
|
32
38
|
end
|
33
39
|
|
34
40
|
def connection
|
@@ -40,7 +46,7 @@ module PgSearch
|
|
40
46
|
end
|
41
47
|
|
42
48
|
def rebuild_sql_template
|
43
|
-
|
49
|
+
<<~SQL.squish
|
44
50
|
INSERT INTO :documents_table (searchable_type, searchable_id, content, created_at, updated_at)
|
45
51
|
SELECT :base_model_name AS searchable_type,
|
46
52
|
:model_table.#{primary_key} AS searchable_id,
|
@@ -74,7 +80,9 @@ module PgSearch
|
|
74
80
|
end
|
75
81
|
|
76
82
|
def content_expressions
|
77
|
-
columns.map
|
83
|
+
columns.map do |column|
|
84
|
+
%{coalesce(:model_table.#{connection.quote_column_name(column)}::text, '')}
|
85
|
+
end.join(" || ' ' || ")
|
78
86
|
end
|
79
87
|
|
80
88
|
def columns
|
@@ -1,18 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "pg_search/multisearch/rebuilder"
|
2
4
|
|
3
5
|
module PgSearch
|
4
6
|
module Multisearch
|
5
7
|
class << self
|
6
|
-
def rebuild(model, clean_up
|
7
|
-
|
8
|
-
|
9
|
-
|
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
|
15
|
+
end
|
16
|
+
|
17
|
+
if transactional
|
18
|
+
model.transaction { execute(model, clean_up) }
|
19
|
+
else
|
20
|
+
execute(model, clean_up)
|
10
21
|
end
|
11
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
|
29
|
+
end
|
12
30
|
end
|
13
31
|
|
14
32
|
class ModelNotMultisearchable < StandardError
|
15
33
|
def initialize(model_class)
|
34
|
+
super
|
16
35
|
@model_class = model_class
|
17
36
|
end
|
18
37
|
|
@@ -22,5 +41,3 @@ module PgSearch
|
|
22
41
|
end
|
23
42
|
end
|
24
43
|
end
|
25
|
-
|
26
|
-
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "active_support/core_ext/class/attribute"
|
2
4
|
|
3
5
|
module PgSearch
|
@@ -5,12 +7,12 @@ module PgSearch
|
|
5
7
|
def self.included(mod)
|
6
8
|
mod.class_eval do
|
7
9
|
has_one :pg_search_document,
|
8
|
-
|
9
|
-
|
10
|
-
|
10
|
+
as: :searchable,
|
11
|
+
class_name: "PgSearch::Document",
|
12
|
+
dependent: :delete
|
11
13
|
|
12
14
|
after_save :update_pg_search_document,
|
13
|
-
|
15
|
+
if: -> { PgSearch.multisearch_enabled? }
|
14
16
|
end
|
15
17
|
end
|
16
18
|
|
@@ -31,6 +33,8 @@ module PgSearch
|
|
31
33
|
end
|
32
34
|
|
33
35
|
def should_update_pg_search_document?
|
36
|
+
return false if pg_search_document.destroyed?
|
37
|
+
|
34
38
|
conditions = Array(pg_search_multisearchable_options[:update_if])
|
35
39
|
conditions.all? { |condition| condition.to_proc.call(self) }
|
36
40
|
end
|
@@ -46,7 +50,7 @@ module PgSearch
|
|
46
50
|
if should_have_document
|
47
51
|
create_or_update_pg_search_document
|
48
52
|
else
|
49
|
-
pg_search_document
|
53
|
+
pg_search_document&.destroy
|
50
54
|
end
|
51
55
|
end
|
52
56
|
|
@@ -54,7 +58,7 @@ module PgSearch
|
|
54
58
|
if !pg_search_document
|
55
59
|
create_pg_search_document(pg_search_document_attrs)
|
56
60
|
elsif should_update_pg_search_document?
|
57
|
-
pg_search_document.
|
61
|
+
pg_search_document.update(pg_search_document_attrs)
|
58
62
|
end
|
59
63
|
end
|
60
64
|
end
|
data/lib/pg_search/normalizer.rb
CHANGED