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.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/.codeclimate.yml +1 -0
  3. data/.editorconfig +10 -0
  4. data/.github/dependabot.yml +11 -0
  5. data/.rubocop.yml +88 -7
  6. data/.travis.yml +11 -19
  7. data/CHANGELOG.md +40 -15
  8. data/Gemfile +1 -1
  9. data/LICENSE +1 -1
  10. data/README.md +46 -45
  11. data/lib/pg_search.rb +13 -56
  12. data/lib/pg_search/document.rb +2 -2
  13. data/lib/pg_search/features/dmetaphone.rb +4 -6
  14. data/lib/pg_search/features/tsearch.rb +13 -12
  15. data/lib/pg_search/migration/templates/add_pg_search_dmetaphone_support_functions.rb.erb +6 -6
  16. data/lib/pg_search/migration/templates/create_pg_search_documents.rb.erb +2 -2
  17. data/lib/pg_search/model.rb +57 -0
  18. data/lib/pg_search/multisearch.rb +10 -1
  19. data/lib/pg_search/multisearch/rebuilder.rb +7 -3
  20. data/lib/pg_search/scope_options.rb +2 -2
  21. data/lib/pg_search/tasks.rb +2 -1
  22. data/lib/pg_search/version.rb +1 -1
  23. data/pg_search.gemspec +9 -5
  24. data/spec/.rubocop.yml +2 -2
  25. data/spec/integration/.rubocop.yml +11 -0
  26. data/spec/integration/associations_spec.rb +27 -66
  27. data/spec/integration/deprecation_spec.rb +33 -0
  28. data/spec/integration/pagination_spec.rb +1 -1
  29. data/spec/integration/pg_search_spec.rb +70 -59
  30. data/spec/integration/single_table_inheritance_spec.rb +2 -2
  31. data/spec/lib/pg_search/configuration/association_spec.rb +10 -8
  32. data/spec/lib/pg_search/configuration/foreign_column_spec.rb +1 -1
  33. data/spec/lib/pg_search/features/dmetaphone_spec.rb +2 -2
  34. data/spec/lib/pg_search/features/trigram_spec.rb +15 -11
  35. data/spec/lib/pg_search/features/tsearch_spec.rb +16 -10
  36. data/spec/lib/pg_search/multisearch/rebuilder_spec.rb +124 -76
  37. data/spec/lib/pg_search/multisearch_spec.rb +49 -30
  38. data/spec/lib/pg_search/multisearchable_spec.rb +155 -101
  39. data/spec/lib/pg_search/normalizer_spec.rb +12 -10
  40. data/spec/lib/pg_search_spec.rb +59 -46
  41. data/spec/spec_helper.rb +13 -4
  42. data/spec/support/database.rb +1 -1
  43. metadata +76 -13
@@ -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
- extend ActiveSupport::Concern
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
- ActiveSupport.on_load(:active_record) do
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)
@@ -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(STDERR)
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
- 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
 
@@ -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) # rubocop:disable Metrics/AbcSize
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
- # 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.
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 options[:prefix])
124
+ (Arel::Nodes.build_quoted(":*") if prefix)
119
125
  ].compact
120
126
 
121
- tsquery_sql = terms.inject do |memo, term|
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 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
@@ -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, clean_up = true)
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| !column_names.include?(column.to_s) }
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
- <<-SQL.strip_heredoc
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(: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))
@@ -58,7 +58,7 @@ module PgSearch
58
58
  end
59
59
 
60
60
  module PgSearchRankTableAliasing
61
- def pg_search_rank_table_alias(include_counter = false)
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
@@ -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, <<-MESSAGE.strip_heredoc unless args.model
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PgSearch
4
- VERSION = '2.2.0'
4
+ VERSION = '2.3.4'
5
5
  end
@@ -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', '>= 4.2'
22
- s.add_dependency 'activesupport', '>= 4.2'
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.68.1'
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.4'
36
+ s.required_ruby_version = '>= 2.5'
33
37
  end
@@ -1,10 +1,10 @@
1
1
  inherit_from:
2
2
  - ../.rubocop.yml
3
3
 
4
- Metrics/LineLength:
4
+ Layout/LineLength:
5
5
  Enabled: false
6
6
 
7
- Lint/HandleExceptions:
7
+ Lint/SuppressedException:
8
8
  Enabled: false
9
9
 
10
10
  Lint/UselessAssignment:
@@ -0,0 +1,11 @@
1
+ inherit_from:
2
+ - ../.rubocop.yml
3
+
4
+ RSpec/DescribeClass:
5
+ Enabled: false
6
+
7
+ RSpec/MultipleExpectations:
8
+ Enabled: false
9
+
10
+ RSpec/ExampleLength:
11
+ Enabled: false