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.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/.github/dependabot.yml +11 -0
  3. data/.github/workflows/ci.yml +80 -0
  4. data/.jrubyrc +1 -0
  5. data/.standard.yml +6 -0
  6. data/CHANGELOG.md +55 -20
  7. data/CODE_OF_CONDUCT.md +76 -0
  8. data/Gemfile +19 -6
  9. data/LICENSE +1 -1
  10. data/README.md +106 -43
  11. data/Rakefile +9 -6
  12. data/lib/pg_search/configuration/column.rb +6 -4
  13. data/lib/pg_search/configuration/foreign_column.rb +1 -1
  14. data/lib/pg_search/configuration.rb +13 -3
  15. data/lib/pg_search/document.rb +9 -9
  16. data/lib/pg_search/features/dmetaphone.rb +5 -7
  17. data/lib/pg_search/features/feature.rb +1 -1
  18. data/lib/pg_search/features/trigram.rb +4 -4
  19. data/lib/pg_search/features/tsearch.rb +26 -24
  20. data/lib/pg_search/migration/dmetaphone_generator.rb +2 -2
  21. data/lib/pg_search/migration/generator.rb +5 -5
  22. data/lib/pg_search/migration/multisearch_generator.rb +2 -2
  23. data/lib/pg_search/migration/templates/add_pg_search_dmetaphone_support_functions.rb.erb +6 -6
  24. data/lib/pg_search/migration/templates/create_pg_search_documents.rb.erb +2 -2
  25. data/lib/pg_search/model.rb +6 -6
  26. data/lib/pg_search/multisearch/rebuilder.rb +2 -2
  27. data/lib/pg_search/multisearch.rb +23 -4
  28. data/lib/pg_search/multisearchable.rb +7 -7
  29. data/lib/pg_search/normalizer.rb +5 -5
  30. data/lib/pg_search/scope_options.rb +31 -13
  31. data/lib/pg_search/tasks.rb +3 -3
  32. data/lib/pg_search/version.rb +1 -1
  33. data/lib/pg_search.rb +5 -5
  34. data/pg_search.gemspec +16 -24
  35. data/spec/.rubocop.yml +20 -7
  36. data/spec/integration/.rubocop.yml +11 -0
  37. data/spec/integration/associations_spec.rb +121 -160
  38. data/spec/integration/deprecation_spec.rb +7 -8
  39. data/spec/integration/pg_search_spec.rb +390 -332
  40. data/spec/integration/single_table_inheritance_spec.rb +5 -5
  41. data/spec/lib/pg_search/configuration/association_spec.rb +21 -19
  42. data/spec/lib/pg_search/configuration/column_spec.rb +13 -1
  43. data/spec/lib/pg_search/configuration/foreign_column_spec.rb +4 -4
  44. data/spec/lib/pg_search/features/dmetaphone_spec.rb +4 -4
  45. data/spec/lib/pg_search/features/trigram_spec.rb +32 -28
  46. data/spec/lib/pg_search/features/tsearch_spec.rb +57 -33
  47. data/spec/lib/pg_search/multisearch/rebuilder_spec.rb +94 -63
  48. data/spec/lib/pg_search/multisearch_spec.rb +57 -29
  49. data/spec/lib/pg_search/multisearchable_spec.rb +160 -107
  50. data/spec/lib/pg_search/normalizer_spec.rb +12 -10
  51. data/spec/lib/pg_search_spec.rb +75 -64
  52. data/spec/spec_helper.rb +21 -9
  53. data/spec/support/database.rb +10 -8
  54. metadata +20 -134
  55. data/.autotest +0 -5
  56. data/.codeclimate.yml +0 -17
  57. data/.rubocop.yml +0 -56
  58. data/.travis.yml +0 -50
data/Rakefile CHANGED
@@ -1,14 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'bundler'
3
+ require "bundler"
4
4
  Bundler::GemHelper.install_tasks
5
5
 
6
- require 'rspec/core/rake_task'
6
+ require "rspec/core/rake_task"
7
7
  RSpec::Core::RakeTask.new(:spec)
8
8
 
9
- require "rubocop/rake_task"
10
- RuboCop::RakeTask.new do |t|
11
- t.options = %w[--display-cop-names]
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 rubocop]
17
+ task default: %w[spec standard undercover]
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'digest'
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.to_s
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(@column_name)
35
+ @connection.quote_column_name(@name)
34
36
  end
35
37
 
36
38
  def expression
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'digest'
3
+ require "digest"
4
4
 
5
5
  module PgSearch
6
6
  class Configuration
@@ -80,7 +80,7 @@ module PgSearch
80
80
  attr_reader :options
81
81
 
82
82
  def default_options
83
- { using: :tsearch }
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 ArgumentError, "the search scope #{@name} must have :against or :associated_against in its options"
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
@@ -1,28 +1,28 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'logger'
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 = 'pg_search_documents'
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(STDERR)
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
- PgSearch.multisearch_options.call(*args)
21
- else
22
- { query: args.first }.merge(PgSearch.multisearch_options)
23
- end
20
+ PgSearch.multisearch_options.call(*args)
21
+ else
22
+ {query: args.first}.merge(PgSearch.multisearch_options)
23
+ end
24
24
 
25
- { against: :content }.merge(options)
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: 'simple')
10
+ options = (options || {}).merge(dictionary: "simple")
9
11
  @tsearch = TSearch.new(query, options, columns, model, dmetaphone_normalizer)
10
12
  end
11
13
 
12
- def conditions
13
- tsearch.conditions
14
- end
14
+ delegate :conditions, to: :tsearch
15
15
 
16
- def rank
17
- tsearch.rank
18
- end
16
+ delegate :rank, to: :tsearch
19
17
 
20
18
  private
21
19
 
@@ -10,7 +10,7 @@ module PgSearch
10
10
  %i[only sort_only]
11
11
  end
12
12
 
13
- delegate :connection, :quoted_table_name, to: :'@model'
13
+ delegate :connection, :quoted_table_name, to: :@model
14
14
 
15
15
  def initialize(query, options, all_columns, model, normalizer)
16
16
  @query = query
@@ -35,17 +35,17 @@ module PgSearch
35
35
 
36
36
  def similarity_function
37
37
  if word_similarity?
38
- 'word_similarity'
38
+ "word_similarity"
39
39
  else
40
- 'similarity'
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 'active_support/deprecation'
4
+ require "active_support/deprecation"
5
5
 
6
6
  module PgSearch
7
7
  module Features
8
- class TSearch < Feature # rubocop:disable Metrics/ClassLength
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 '' unless options[:highlight].is_a?(Hash)
39
+ return "" unless options[:highlight].is_a?(Hash)
40
40
 
41
41
  headline_options
42
42
  .merge(deprecated_headline_options)
43
- .map { |key, value| "#{key} = #{value}" unless value.nil? }
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
- ActiveSupport::Deprecation.warn(
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 = /['?\\:‘’]/.freeze
99
+ DISALLOWED_TSQUERY_CHARACTERS = /['?\\:‘’ʻʼ]/
99
100
 
100
- def tsquery_for_term(unsanitized_term) # rubocop:disable Metrics/AbcSize
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
- # After this, the SQL expression evaluates to a string containing the term surrounded by single-quotes.
111
- # If :prefix is true, then the term will have :* appended to the end.
112
- # If :negated is true, then the term will have ! prepended to the front.
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('!') if negated),
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 options[:prefix])
125
+ (Arel::Nodes.build_quoted(":*") if prefix)
119
126
  ].compact
120
127
 
121
- tsquery_sql = terms.inject do |memo, term|
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(" ").compact
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 'pg_search/migration/generator'
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
- 'add_pg_search_dmetaphone_support_functions'
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 'active_record'
4
- require 'rails/generators/base'
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('templates', __dir__)
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('%Y%m%d%H%M%S')}_#{migration_name}.rb"
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('../../../sql', __dir__)
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 'pg_search/migration/generator'
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
- 'create_pg_search_documents'
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 self.up
2
+ def up
3
3
  say_with_time("Adding support functions for pg_search :dmetaphone") do
4
- execute <<-'SQL'
5
- <%= read_sql_file "dmetaphone" %>
4
+ execute <<~'SQL'.squish
5
+ <%= indent(read_sql_file("dmetaphone"), 8) %>
6
6
  SQL
7
7
  end
8
8
  end
9
9
 
10
- def self.down
10
+ def down
11
11
  say_with_time("Dropping support functions for pg_search :dmetaphone") do
12
- execute <<-'SQL'
13
- <%= read_sql_file "uninstall_dmetaphone" %>
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 self.up
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 self.down
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
@@ -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
- 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
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| !column_names.include?(column.to_s) }
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
- <<-SQL.strip_heredoc
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, clean_up = true)
9
- model.transaction do
10
- PgSearch::Document.where(searchable_type: model.base_class.name).delete_all if clean_up
11
- Rebuilder.new(model).rebuild
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
- as: :searchable,
11
- class_name: "PgSearch::Document",
12
- dependent: :delete
10
+ as: :searchable,
11
+ class_name: "PgSearch::Document",
12
+ dependent: :delete
13
13
 
14
14
  after_save :update_pg_search_document,
15
- if: -> { PgSearch.multisearch_enabled? }
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 # rubocop:disable Metrics/AbcSize
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
@@ -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
- when Arel::Nodes::Node
14
- sql_expression
15
- else
16
- Arel.sql(sql_expression)
17
- end
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(:include_counter)
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(pg_search_highlight_field)
41
- end
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 = false)
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
- config.features
97
- .reject { |_feature_name, feature_options| feature_options && feature_options[:sort_only] }
98
- .map { |feature_name, _feature_options| feature_for(feature_name).conditions }
99
- .inject { |accumulator, expression| Arel::Nodes::Or.new(accumulator, expression) }
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.class_eval { include PgSearchRankTableAliasing }
173
+ new_scope.instance_eval { extend PgSearchRankTableAliasing }
156
174
  end
157
175
  end
158
176
  end