pg_search 2.3.0 → 2.3.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/.codeclimate.yml +1 -0
  3. data/.github/dependabot.yml +11 -0
  4. data/.jrubyrc +1 -0
  5. data/.rubocop.yml +85 -7
  6. data/.travis.yml +14 -22
  7. data/CHANGELOG.md +41 -16
  8. data/CODE_OF_CONDUCT.md +76 -0
  9. data/Gemfile +1 -1
  10. data/LICENSE +1 -1
  11. data/README.md +60 -18
  12. data/lib/pg_search.rb +4 -6
  13. data/lib/pg_search/document.rb +1 -1
  14. data/lib/pg_search/features/dmetaphone.rb +4 -6
  15. data/lib/pg_search/features/tsearch.rb +13 -12
  16. data/lib/pg_search/migration/templates/add_pg_search_dmetaphone_support_functions.rb.erb +6 -6
  17. data/lib/pg_search/migration/templates/create_pg_search_documents.rb.erb +2 -2
  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 +3 -3
  21. data/lib/pg_search/tasks.rb +2 -1
  22. data/lib/pg_search/version.rb +1 -1
  23. data/pg_search.gemspec +11 -7
  24. data/spec/.rubocop.yml +2 -2
  25. data/spec/integration/.rubocop.yml +11 -0
  26. data/spec/integration/associations_spec.rb +17 -56
  27. data/spec/integration/deprecation_spec.rb +1 -1
  28. data/spec/integration/pg_search_spec.rb +62 -51
  29. data/spec/lib/pg_search/configuration/association_spec.rb +8 -6
  30. data/spec/lib/pg_search/features/dmetaphone_spec.rb +2 -2
  31. data/spec/lib/pg_search/features/trigram_spec.rb +15 -11
  32. data/spec/lib/pg_search/features/tsearch_spec.rb +16 -10
  33. data/spec/lib/pg_search/multisearch/rebuilder_spec.rb +116 -71
  34. data/spec/lib/pg_search/multisearch_spec.rb +48 -29
  35. data/spec/lib/pg_search/multisearchable_spec.rb +150 -97
  36. data/spec/lib/pg_search/normalizer_spec.rb +12 -10
  37. data/spec/lib/pg_search_spec.rb +66 -55
  38. data/spec/spec_helper.rb +13 -4
  39. data/spec/support/database.rb +1 -1
  40. metadata +78 -17
@@ -15,8 +15,10 @@ require "pg_search/scope_options"
15
15
  require "pg_search/version"
16
16
 
17
17
  module PgSearch
18
+ autoload :Document, "pg_search/document"
19
+
18
20
  def self.included(base)
19
- ActiveSupport::Deprecation.warn <<-MESSAGE.strip_heredoc
21
+ ActiveSupport::Deprecation.warn <<~MESSAGE
20
22
  Directly including `PgSearch` into an Active Record model is deprecated and will be removed in pg_search 3.0.
21
23
 
22
24
  Please replace `include PgSearch` with `include PgSearch::Model`.
@@ -67,8 +69,4 @@ module PgSearch
67
69
  end
68
70
  end
69
71
 
70
- ActiveSupport.on_load(:active_record) do
71
- require "pg_search/document"
72
- end
73
-
74
- require "pg_search/railtie" if defined?(Rails)
72
+ require "pg_search/railtie" if defined?(Rails::Railtie)
@@ -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
@@ -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
@@ -152,7 +152,7 @@ module PgSearch
152
152
  return scope if scope.included_modules.include?(PgSearchRankTableAliasing)
153
153
 
154
154
  scope.all.spawn.tap do |new_scope|
155
- new_scope.class_eval { include PgSearchRankTableAliasing }
155
+ new_scope.instance_eval { extend PgSearchRankTableAliasing }
156
156
  end
157
157
  end
158
158
  end
@@ -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.3.0'
4
+ VERSION = '2.3.5'
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
- s.add_development_dependency 'rspec', '>= 3.3'
27
- s.add_development_dependency 'rubocop', '>= 0.68.1'
26
+ s.add_development_dependency 'rspec'
27
+ s.add_development_dependency 'rubocop'
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'
30
- s.add_development_dependency 'with_model', '>= 1.2'
33
+ s.add_development_dependency 'warning'
34
+ s.add_development_dependency 'with_model'
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
@@ -2,8 +2,9 @@
2
2
 
3
3
  require "spec_helper"
4
4
 
5
- describe PgSearch do
6
- context "joining to another table" do
5
+ # rubocop:disable RSpec/NestedGroups
6
+ describe "a pg_search_scope" do
7
+ context "when joining to another table" do
7
8
  context "without an :against" do
8
9
  with_model :AssociatedModel do
9
10
  table do |t|
@@ -41,7 +42,7 @@ describe PgSearch do
41
42
  end
42
43
  end
43
44
 
44
- context "through a belongs_to association" do
45
+ context "via a belongs_to association" do
45
46
  with_model :AssociatedModel do
46
47
  table do |t|
47
48
  t.string 'title'
@@ -77,7 +78,7 @@ describe PgSearch do
77
78
  end
78
79
  end
79
80
 
80
- context "through a has_many association" do
81
+ context "via a has_many association" do
81
82
  with_model :AssociatedModelWithHasMany do
82
83
  table do |t|
83
84
  t.string 'title'
@@ -141,8 +142,8 @@ describe PgSearch do
141
142
  end
142
143
  end
143
144
 
144
- context "across multiple associations" do
145
- context "on different tables" do
145
+ context "when across multiple associations" do
146
+ context "when on different tables" do
146
147
  with_model :FirstAssociatedModel do
147
148
  table do |t|
148
149
  t.string 'title'
@@ -207,7 +208,7 @@ describe PgSearch do
207
208
  end
208
209
  end
209
210
 
210
- context "on the same table" do
211
+ context "when on the same table" do
211
212
  with_model :DoublyAssociatedModel do
212
213
  table do |t|
213
214
  t.string 'title'
@@ -268,7 +269,7 @@ describe PgSearch do
268
269
  end
269
270
  end
270
271
 
271
- context "against multiple attributes on one association" do
272
+ context "when against multiple attributes on one association" do
272
273
  with_model :AssociatedModel do
273
274
  table do |t|
274
275
  t.string 'title'
@@ -289,7 +290,7 @@ describe PgSearch do
289
290
  end
290
291
  end
291
292
 
292
- it "should only do one join" do
293
+ it "joins only once" do
293
294
  included = [
294
295
  ModelWithAssociation.create!(
295
296
  another_model: AssociatedModel.create!(
@@ -321,7 +322,7 @@ describe PgSearch do
321
322
  end
322
323
  end
323
324
 
324
- context "against non-text columns" do
325
+ context "when against non-text columns" do
325
326
  with_model :AssociatedModel do
326
327
  table do |t|
327
328
  t.integer 'number'
@@ -342,7 +343,7 @@ describe PgSearch do
342
343
  end
343
344
  end
344
345
 
345
- it "should cast the columns to text" do
346
+ it "casts the columns to text" do
346
347
  associated = AssociatedModel.create!(number: 123)
347
348
  included = [
348
349
  Model.create!(number: 123, another_model: associated),
@@ -395,7 +396,7 @@ describe PgSearch do
395
396
  end
396
397
  end
397
398
 
398
- context "merging a pg_search_scope into another model's scope" do
399
+ context "when merging a pg_search_scope into another model's scope" do
399
400
  with_model :ModelWithAssociation do
400
401
  model do
401
402
  has_many :associated_models
@@ -416,7 +417,7 @@ describe PgSearch do
416
417
  end
417
418
  end
418
419
 
419
- it "should find records of the other model" do
420
+ it "finds records of the other model" do
420
421
  included_associated_1 = AssociatedModel.create(content: "foo bar")
421
422
  included_associated_2 = AssociatedModel.create(content: "foo baz")
422
423
  excluded_associated_1 = AssociatedModel.create(content: "baz quux")
@@ -441,7 +442,7 @@ describe PgSearch do
441
442
  end
442
443
  end
443
444
 
444
- context "chained onto a has_many association" do
445
+ context "when chained onto a has_many association" do
445
446
  with_model :Company do
446
447
  model do
447
448
  has_many :positions
@@ -461,48 +462,7 @@ describe PgSearch do
461
462
  end
462
463
 
463
464
  # https://github.com/Casecommons/pg_search/issues/106
464
- it "should handle numbers in a trigram query properly" do
465
- company = Company.create!
466
- another_company = Company.create!
467
-
468
- included = [
469
- Position.create!(company_id: company.id, title: "teller 1")
470
- ]
471
-
472
- excluded = [
473
- Position.create!(company_id: nil, title: "teller 1"),
474
- Position.create!(company_id: another_company.id, title: "teller 1"),
475
- Position.create!(company_id: company.id, title: "penn 1")
476
- ]
477
-
478
- results = company.positions.search('teller 1')
479
-
480
- expect(results).to include(*included)
481
- expect(results).not_to include(*excluded)
482
- end
483
- end
484
-
485
- context "chained onto a has_many association" do
486
- with_model :Company do
487
- model do
488
- has_many :positions
489
- end
490
- end
491
-
492
- with_model :Position do
493
- table do |t|
494
- t.string :title
495
- t.belongs_to :company
496
- end
497
-
498
- model do
499
- include PgSearch::Model
500
- pg_search_scope :search, against: :title, using: %i[tsearch trigram]
501
- end
502
- end
503
-
504
- # https://github.com/Casecommons/pg_search/issues/106
505
- it "should handle numbers in a trigram query properly" do
465
+ it "handles numbers in a trigram query properly" do
506
466
  company = Company.create!
507
467
  another_company = Company.create!
508
468
 
@@ -524,3 +484,4 @@ describe PgSearch do
524
484
  end
525
485
  end
526
486
  end
487
+ # rubocop:enable RSpec/NestedGroups