pg_search 2.2.0 → 2.3.4

Sign up to get free protection for your applications and to get access to all the features.
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