pg_search 2.1.7 → 2.3.3

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 (45) 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 +89 -7
  6. data/.travis.yml +11 -19
  7. data/CHANGELOG.md +38 -14
  8. data/Gemfile +1 -1
  9. data/LICENSE +1 -1
  10. data/README.md +74 -42
  11. data/lib/pg_search.rb +15 -58
  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/trigram.rb +29 -5
  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/model.rb +59 -0
  19. data/lib/pg_search/multisearch.rb +10 -1
  20. data/lib/pg_search/multisearch/rebuilder.rb +7 -3
  21. data/lib/pg_search/multisearchable.rb +4 -4
  22. data/lib/pg_search/scope_options.rb +2 -10
  23. data/lib/pg_search/tasks.rb +2 -1
  24. data/lib/pg_search/version.rb +1 -1
  25. data/pg_search.gemspec +10 -5
  26. data/spec/.rubocop.yml +2 -2
  27. data/spec/integration/.rubocop.yml +11 -0
  28. data/spec/integration/associations_spec.rb +36 -75
  29. data/spec/integration/deprecation_spec.rb +33 -0
  30. data/spec/integration/pagination_spec.rb +1 -1
  31. data/spec/integration/pg_search_spec.rb +199 -188
  32. data/spec/integration/single_table_inheritance_spec.rb +2 -2
  33. data/spec/lib/pg_search/configuration/association_spec.rb +10 -8
  34. data/spec/lib/pg_search/configuration/foreign_column_spec.rb +3 -3
  35. data/spec/lib/pg_search/features/dmetaphone_spec.rb +2 -2
  36. data/spec/lib/pg_search/features/trigram_spec.rb +48 -19
  37. data/spec/lib/pg_search/features/tsearch_spec.rb +16 -10
  38. data/spec/lib/pg_search/multisearch/rebuilder_spec.rb +124 -76
  39. data/spec/lib/pg_search/multisearch_spec.rb +49 -30
  40. data/spec/lib/pg_search/multisearchable_spec.rb +155 -101
  41. data/spec/lib/pg_search/normalizer_spec.rb +12 -10
  42. data/spec/lib/pg_search_spec.rb +62 -46
  43. data/spec/spec_helper.rb +13 -4
  44. data/spec/support/database.rb +1 -1
  45. metadata +90 -13
@@ -11,7 +11,7 @@ describe "a pg_search_scope on an STI subclass" do
11
11
  end
12
12
 
13
13
  model do
14
- include PgSearch
14
+ include PgSearch::Model
15
15
  pg_search_scope :search_content, against: :content
16
16
  end
17
17
  end
@@ -51,7 +51,7 @@ describe "a pg_search_scope on an STI subclass" do
51
51
  end
52
52
 
53
53
  model do
54
- include PgSearch
54
+ include PgSearch::Model
55
55
  self.inheritance_column = 'custom_type'
56
56
  pg_search_scope :search_content, against: :content
57
57
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "spec_helper"
4
4
 
5
+ # rubocop:disable RSpec/NestedGroups
5
6
  describe PgSearch::Configuration::Association do
6
7
  with_model :Avatar do
7
8
  table do |t|
@@ -17,7 +18,7 @@ describe PgSearch::Configuration::Association do
17
18
  end
18
19
 
19
20
  model do
20
- include PgSearch
21
+ include PgSearch::Model
21
22
  has_one :avatar, class_name: "Avatar"
22
23
  belongs_to :site
23
24
 
@@ -32,14 +33,14 @@ describe PgSearch::Configuration::Association do
32
33
  end
33
34
 
34
35
  model do
35
- include PgSearch
36
+ include PgSearch::Model
36
37
  has_many :users, class_name: "User"
37
38
 
38
39
  pg_search_scope :with_users, associated_against: { users: :name }
39
40
  end
40
41
  end
41
42
 
42
- context "has_one" do
43
+ context "with has_one" do
43
44
  let(:association) { described_class.new(User, :avatar, :url) }
44
45
 
45
46
  describe "#table_name" do
@@ -50,7 +51,7 @@ describe PgSearch::Configuration::Association do
50
51
 
51
52
  describe "#join" do
52
53
  let(:expected_sql) do
53
- <<-SQL.gsub(/\s+/, ' ').strip
54
+ <<~SQL.squish
54
55
  LEFT OUTER JOIN
55
56
  (SELECT model_id AS id,
56
57
  #{column_select} AS #{association.columns.first.alias}
@@ -70,7 +71,7 @@ describe PgSearch::Configuration::Association do
70
71
  end
71
72
  end
72
73
 
73
- context "belongs_to" do
74
+ context "with belongs_to" do
74
75
  let(:association) { described_class.new(User, :site, :title) }
75
76
 
76
77
  describe "#table_name" do
@@ -81,7 +82,7 @@ describe PgSearch::Configuration::Association do
81
82
 
82
83
  describe "#join" do
83
84
  let(:expected_sql) do
84
- <<-SQL.gsub(/\s+/, ' ').strip
85
+ <<~SQL.squish
85
86
  LEFT OUTER JOIN
86
87
  (SELECT model_id AS id,
87
88
  #{column_select} AS #{association.columns.first.alias}
@@ -101,7 +102,7 @@ describe PgSearch::Configuration::Association do
101
102
  end
102
103
  end
103
104
 
104
- context "has_many" do
105
+ context "with has_many" do
105
106
  let(:association) { described_class.new(Site, :users, :name) }
106
107
 
107
108
  describe "#table_name" do
@@ -112,7 +113,7 @@ describe PgSearch::Configuration::Association do
112
113
 
113
114
  describe "#join" do
114
115
  let(:expected_sql) do
115
- <<-SQL.gsub(/\s+/, ' ').strip
116
+ <<~SQL.squish
116
117
  LEFT OUTER JOIN
117
118
  (SELECT model_id AS id,
118
119
  string_agg(\"#{association.table_name}\".\"name\"::text, ' ') AS #{association.columns.first.alias}
@@ -138,3 +139,4 @@ describe PgSearch::Configuration::Association do
138
139
  end
139
140
  end
140
141
  end
142
+ # rubocop:enable RSpec/NestedGroups
@@ -17,7 +17,7 @@ describe PgSearch::Configuration::ForeignColumn do
17
17
  end
18
18
 
19
19
  model do
20
- include PgSearch
20
+ include PgSearch::Model
21
21
  belongs_to :another_model, class_name: 'AssociatedModel'
22
22
 
23
23
  pg_search_scope :with_another, associated_against: { another_model: :title }
@@ -26,8 +26,8 @@ describe PgSearch::Configuration::ForeignColumn do
26
26
 
27
27
  it "returns a consistent string" do
28
28
  association = PgSearch::Configuration::Association.new(Model,
29
- :another_model,
30
- :title)
29
+ :another_model,
30
+ :title)
31
31
  foreign_column = described_class.new("title", nil, Model, association)
32
32
 
33
33
  column_alias = foreign_column.alias
@@ -18,7 +18,7 @@ describe PgSearch::Features::DMetaphone do
18
18
  PgSearch::Configuration::Column.new(:content, nil, Model)
19
19
  ]
20
20
  options = {}
21
- config = double(:config, ignore: [])
21
+ config = instance_double("PgSearch::Configuration", :config, ignore: [])
22
22
  normalizer = PgSearch::Normalizer.new(config)
23
23
 
24
24
  feature = described_class.new(query, options, columns, Model, normalizer)
@@ -43,7 +43,7 @@ describe PgSearch::Features::DMetaphone do
43
43
  PgSearch::Configuration::Column.new(:content, nil, Model)
44
44
  ]
45
45
  options = {}
46
- config = double(:config, ignore: [])
46
+ config = instance_double("PgSearch::Configuration", :config, ignore: [])
47
47
  normalizer = PgSearch::Normalizer.new(config)
48
48
 
49
49
  feature = described_class.new(query, options, columns, Model, normalizer)
@@ -3,8 +3,10 @@
3
3
  require 'spec_helper'
4
4
  require 'ostruct'
5
5
 
6
+ # rubocop:disable RSpec/MultipleMemoizedHelpers, RSpec/NestedGroups
6
7
  describe PgSearch::Features::Trigram do
7
8
  subject(:feature) { described_class.new(query, options, columns, Model, normalizer) }
9
+
8
10
  let(:query) { 'lolwut' }
9
11
  let(:options) { {} }
10
12
  let(:columns) {
@@ -17,8 +19,10 @@ describe PgSearch::Features::Trigram do
17
19
  let(:config) { OpenStruct.new(ignore: []) }
18
20
 
19
21
  let(:coalesced_columns) do
20
- <<-SQL.strip_heredoc.chomp
21
- coalesce(#{Model.quoted_table_name}."name"::text, '') || ' ' || coalesce(#{Model.quoted_table_name}."content"::text, '')
22
+ <<~SQL.squish
23
+ coalesce(#{Model.quoted_table_name}."name"::text, '')
24
+ || ' '
25
+ || coalesce(#{Model.quoted_table_name}."content"::text, '')
22
26
  SQL
23
27
  end
24
28
 
@@ -32,44 +36,68 @@ describe PgSearch::Features::Trigram do
32
36
  describe 'conditions' do
33
37
  it 'escapes the search document and query' do
34
38
  config.ignore = []
35
- expect(feature.conditions.to_sql).to eq("((#{coalesced_columns}) % '#{query}')")
39
+ expect(feature.conditions.to_sql).to eq("('#{query}' % (#{coalesced_columns}))")
40
+ end
41
+
42
+ context 'when searching by word_similarity' do
43
+ let(:options) do
44
+ { word_similarity: true }
45
+ end
46
+
47
+ it 'uses the "<%" operator when searching by word_similarity' do
48
+ config.ignore = []
49
+ expect(feature.conditions.to_sql).to eq("('#{query}' <% (#{coalesced_columns}))")
50
+ end
36
51
  end
37
52
 
38
- context 'ignoring accents' do
53
+ context 'when ignoring accents' do
39
54
  it 'escapes the search document and query, but not the accent function' do
40
55
  config.ignore = [:accents]
41
- expect(feature.conditions.to_sql).to eq("((unaccent(#{coalesced_columns})) % unaccent('#{query}'))")
56
+ expect(feature.conditions.to_sql).to eq("(unaccent('#{query}') % (unaccent(#{coalesced_columns})))")
42
57
  end
43
58
  end
44
59
 
45
60
  context 'when a threshold is specified' do
46
- let(:options) do
47
- { threshold: 0.5 }
61
+ context 'when searching by similarity' do
62
+ let(:options) do
63
+ { threshold: 0.5 }
64
+ end
65
+
66
+ it 'uses a minimum similarity expression instead of the "%" operator' do
67
+ expect(feature.conditions.to_sql).to eq(
68
+ "(similarity('#{query}', (#{coalesced_columns})) >= 0.5)"
69
+ )
70
+ end
48
71
  end
49
72
 
50
- it 'uses a minimum similarity expression instead of the "%" operator' do
51
- expect(feature.conditions.to_sql).to eq(
52
- "(similarity((#{coalesced_columns}), '#{query}') >= 0.5)"
53
- )
73
+ context 'when searching by word_similarity' do
74
+ let(:options) do
75
+ { threshold: 0.5, word_similarity: true }
76
+ end
77
+
78
+ it 'uses a minimum similarity expression instead of the "<%" operator' do
79
+ expect(feature.conditions.to_sql).to eq(
80
+ "(word_similarity('#{query}', (#{coalesced_columns})) >= 0.5)"
81
+ )
82
+ end
54
83
  end
55
84
  end
56
85
 
57
- context 'only certain columns are selected' do
58
- context 'one column' do
86
+ context 'when only certain columns are selected' do
87
+ context 'with one column' do
59
88
  let(:options) { { only: :name } }
60
89
 
61
90
  it 'only searches against the select column' do
62
- options = { only: :name }
63
91
  coalesced_column = "coalesce(#{Model.quoted_table_name}.\"name\"::text, '')"
64
- expect(feature.conditions.to_sql).to eq("((#{coalesced_column}) % '#{query}')")
92
+ expect(feature.conditions.to_sql).to eq("('#{query}' % (#{coalesced_column}))")
65
93
  end
66
94
  end
67
- context 'multiple columns' do
95
+
96
+ context 'with multiple columns' do
68
97
  let(:options) { { only: %i[name content] } }
69
98
 
70
99
  it 'concatenates when multiples columns are selected' do
71
- options = { only: %i[name content] }
72
- expect(feature.conditions.to_sql).to eq("((#{coalesced_columns}) % '#{query}')")
100
+ expect(feature.conditions.to_sql).to eq("('#{query}' % (#{coalesced_columns}))")
73
101
  end
74
102
  end
75
103
  end
@@ -77,7 +105,8 @@ describe PgSearch::Features::Trigram do
77
105
 
78
106
  describe '#rank' do
79
107
  it 'returns an expression using the similarity() function' do
80
- expect(feature.rank.to_sql).to eq("(similarity((#{coalesced_columns}), '#{query}'))")
108
+ expect(feature.rank.to_sql).to eq("(similarity('#{query}', (#{coalesced_columns})))")
81
109
  end
82
110
  end
83
111
  end
112
+ # rubocop:enable RSpec/MultipleMemoizedHelpers, RSpec/NestedGroups
@@ -19,7 +19,7 @@ describe PgSearch::Features::TSearch do
19
19
  PgSearch::Configuration::Column.new(:content, nil, Model)
20
20
  ]
21
21
  options = {}
22
- config = double(:config, ignore: [])
22
+ config = instance_double("PgSearch::Configuration", :config, ignore: [])
23
23
  normalizer = PgSearch::Normalizer.new(config)
24
24
 
25
25
  feature = described_class.new(query, options, columns, Model, normalizer)
@@ -44,7 +44,7 @@ describe PgSearch::Features::TSearch do
44
44
  PgSearch::Configuration::Column.new(:content, nil, Model)
45
45
  ]
46
46
  options = {}
47
- config = double(:config, ignore: [])
47
+ config = instance_double("PgSearch::Configuration", :config, ignore: [])
48
48
  normalizer = PgSearch::Normalizer.new(config)
49
49
 
50
50
  feature = described_class.new(query, options, columns, Model, normalizer)
@@ -61,7 +61,7 @@ describe PgSearch::Features::TSearch do
61
61
  PgSearch::Configuration::Column.new(:content, nil, Model)
62
62
  ]
63
63
  options = { negation: true }
64
- config = double(:config, ignore: [])
64
+ config = instance_double("PgSearch::Configuration", :config, ignore: [])
65
65
  normalizer = PgSearch::Normalizer.new(config)
66
66
 
67
67
  feature = described_class.new(query, options, columns, Model, normalizer)
@@ -79,7 +79,7 @@ describe PgSearch::Features::TSearch do
79
79
  PgSearch::Configuration::Column.new(:content, nil, Model)
80
80
  ]
81
81
  options = { negation: false }
82
- config = double(:config, ignore: [])
82
+ config = instance_double("PgSearch::Configuration", :config, ignore: [])
83
83
  normalizer = PgSearch::Normalizer.new(config)
84
84
 
85
85
  feature = described_class.new(query, options, columns, Model, normalizer)
@@ -97,7 +97,7 @@ describe PgSearch::Features::TSearch do
97
97
  PgSearch::Configuration::Column.new(:content, nil, Model)
98
98
  ]
99
99
  options = { tsvector_column: "my_tsvector" }
100
- config = double(:config, ignore: [])
100
+ config = instance_double("PgSearch::Configuration", :config, ignore: [])
101
101
  normalizer = PgSearch::Normalizer.new(config)
102
102
 
103
103
  feature = described_class.new(query, options, columns, Model, normalizer)
@@ -115,7 +115,7 @@ describe PgSearch::Features::TSearch do
115
115
  PgSearch::Configuration::Column.new(:content, nil, Model)
116
116
  ]
117
117
  options = { tsvector_column: ["tsvector1", "tsvector2"] }
118
- config = double(:config, ignore: [])
118
+ config = instance_double("PgSearch::Configuration", :config, ignore: [])
119
119
  normalizer = PgSearch::Normalizer.new(config)
120
120
 
121
121
  feature = described_class.new(query, options, columns, Model, normalizer)
@@ -141,7 +141,7 @@ describe PgSearch::Features::TSearch do
141
141
  ]
142
142
  options = {}
143
143
 
144
- config = double(:config, ignore: [])
144
+ config = instance_double("PgSearch::Configuration", :config, ignore: [])
145
145
  normalizer = PgSearch::Normalizer.new(config)
146
146
 
147
147
  feature = described_class.new(query, options, columns, Model, normalizer)
@@ -151,6 +151,7 @@ describe PgSearch::Features::TSearch do
151
151
  end
152
152
 
153
153
  context "when options[:dictionary] is passed" do
154
+ # rubocop:disable RSpec/ExampleLength
154
155
  it 'uses the provided dictionary' do
155
156
  query = "query"
156
157
  columns = [
@@ -165,7 +166,7 @@ describe PgSearch::Features::TSearch do
165
166
  }
166
167
  }
167
168
 
168
- config = double(:config, ignore: [])
169
+ config = instance_double("PgSearch::Configuration", :config, ignore: [])
169
170
  normalizer = PgSearch::Normalizer.new(config)
170
171
 
171
172
  feature = described_class.new(query, options, columns, Model, normalizer)
@@ -174,9 +175,11 @@ describe PgSearch::Features::TSearch do
174
175
 
175
176
  expect(feature.highlight.to_sql).to eq(expected_sql)
176
177
  end
178
+ # rubocop:enable RSpec/ExampleLength
177
179
  end
178
180
 
179
181
  context "when options[:highlight] has options set" do
182
+ # rubocop:disable RSpec/ExampleLength
180
183
  it "passes the options to ts_headline" do
181
184
  query = "query"
182
185
  columns = [
@@ -195,7 +198,7 @@ describe PgSearch::Features::TSearch do
195
198
  }
196
199
  }
197
200
 
198
- config = double(:config, ignore: [])
201
+ config = instance_double("PgSearch::Configuration", :config, ignore: [])
199
202
  normalizer = PgSearch::Normalizer.new(config)
200
203
 
201
204
  feature = described_class.new(query, options, columns, Model, normalizer)
@@ -204,7 +207,9 @@ describe PgSearch::Features::TSearch do
204
207
 
205
208
  expect(feature.highlight.to_sql).to eq(expected_sql)
206
209
  end
210
+ # rubocop:enable RSpec/ExampleLength
207
211
 
212
+ # rubocop:disable RSpec/ExampleLength
208
213
  it "passes deprecated options to ts_headline" do
209
214
  query = "query"
210
215
  columns = [
@@ -223,7 +228,7 @@ describe PgSearch::Features::TSearch do
223
228
  }
224
229
  }
225
230
 
226
- config = double(:config, ignore: [])
231
+ config = instance_double("PgSearch::Configuration", :config, ignore: [])
227
232
  normalizer = PgSearch::Normalizer.new(config)
228
233
 
229
234
  feature = described_class.new(query, options, columns, Model, normalizer)
@@ -233,6 +238,7 @@ describe PgSearch::Features::TSearch do
233
238
 
234
239
  expect(highlight_sql).to eq(expected_sql)
235
240
  end
241
+ # rubocop:enable RSpec/ExampleLength
236
242
  end
237
243
  end
238
244
  end
@@ -2,20 +2,16 @@
2
2
 
3
3
  require "spec_helper"
4
4
 
5
- def has_microsecond_precision?
6
- (ActiveRecord::VERSION::MAJOR == 4 && ActiveRecord::VERSION::MINOR >= 1) ||
7
- (ActiveRecord::VERSION::MAJOR == 4 && ActiveRecord::VERSION::MINOR == 0 && ActiveRecord::VERSION::TINY >= 1)
8
- end
9
-
5
+ # rubocop:disable RSpec/NestedGroups
10
6
  describe PgSearch::Multisearch::Rebuilder do
11
- with_table "pg_search_documents", {}, &DOCUMENTS_SCHEMA
7
+ with_table "pg_search_documents", &DOCUMENTS_SCHEMA
12
8
 
13
9
  describe 'when initialized with a model that is not multisearchable' do
14
10
  with_model :not_multisearchable
15
11
 
16
12
  it 'raises an exception' do
17
13
  expect {
18
- PgSearch::Multisearch::Rebuilder.new(NotMultisearchable)
14
+ described_class.new(NotMultisearchable)
19
15
  }.to raise_exception(
20
16
  PgSearch::Multisearch::ModelNotMultisearchable,
21
17
  "NotMultisearchable is not multisearchable. See PgSearch::ClassMethods#multisearchable"
@@ -25,10 +21,10 @@ describe PgSearch::Multisearch::Rebuilder do
25
21
 
26
22
  describe "#rebuild" do
27
23
  context "when the model defines .rebuild_pg_search_documents" do
28
- context "and multisearchable is not conditional" do
24
+ context "when multisearchable is not conditional" do
29
25
  with_model :Model do
30
26
  model do
31
- include PgSearch
27
+ include PgSearch::Model
32
28
  multisearchable
33
29
 
34
30
  def rebuild_pg_search_documents
@@ -36,14 +32,18 @@ describe PgSearch::Multisearch::Rebuilder do
36
32
  end
37
33
  end
38
34
 
39
- it "should call .rebuild_pg_search_documents" do
40
- rebuilder = PgSearch::Multisearch::Rebuilder.new(Model)
41
- expect(Model).to receive(:rebuild_pg_search_documents)
42
- rebuilder.rebuild
35
+ it "calls .rebuild_pg_search_documents" do
36
+ rebuilder = described_class.new(Model)
37
+
38
+ without_partial_double_verification do
39
+ allow(Model).to receive(:rebuild_pg_search_documents)
40
+ rebuilder.rebuild
41
+ expect(Model).to have_received(:rebuild_pg_search_documents)
42
+ end
43
43
  end
44
44
  end
45
45
 
46
- context "and multisearchable is conditional" do
46
+ context "when multisearchable is conditional" do
47
47
  %i[if unless].each do |conditional_key|
48
48
  context "via :#{conditional_key}" do
49
49
  with_model :Model do
@@ -52,7 +52,7 @@ describe PgSearch::Multisearch::Rebuilder do
52
52
  end
53
53
 
54
54
  model do
55
- include PgSearch
55
+ include PgSearch::Model
56
56
  multisearchable conditional_key => :active?
57
57
 
58
58
  def rebuild_pg_search_documents
@@ -60,10 +60,14 @@ describe PgSearch::Multisearch::Rebuilder do
60
60
  end
61
61
  end
62
62
 
63
- it "should call .rebuild_pg_search_documents" do
64
- rebuilder = PgSearch::Multisearch::Rebuilder.new(Model)
65
- expect(Model).to receive(:rebuild_pg_search_documents)
66
- rebuilder.rebuild
63
+ it "calls .rebuild_pg_search_documents" do
64
+ rebuilder = described_class.new(Model)
65
+
66
+ without_partial_double_verification do
67
+ allow(Model).to receive(:rebuild_pg_search_documents)
68
+ rebuilder.rebuild
69
+ expect(Model).to have_received(:rebuild_pg_search_documents)
70
+ end
67
71
  end
68
72
  end
69
73
  end
@@ -71,7 +75,7 @@ describe PgSearch::Multisearch::Rebuilder do
71
75
  end
72
76
 
73
77
  context "when the model does not define .rebuild_pg_search_documents" do
74
- context "and multisearchable is not conditional" do
78
+ context "when multisearchable is not conditional" do
75
79
  context "when :against only includes columns" do
76
80
  with_model :Model do
77
81
  table do |t|
@@ -79,13 +83,13 @@ describe PgSearch::Multisearch::Rebuilder do
79
83
  end
80
84
 
81
85
  model do
82
- include PgSearch
86
+ include PgSearch::Model
83
87
  multisearchable against: :name
84
88
  end
85
89
  end
86
90
 
87
- it "should not call :rebuild_pg_search_documents" do
88
- rebuilder = PgSearch::Multisearch::Rebuilder.new(Model)
91
+ it "does not call :rebuild_pg_search_documents" do
92
+ rebuilder = described_class.new(Model)
89
93
 
90
94
  # stub respond_to? to return false since should_not_receive defines the method
91
95
  original_respond_to = Model.method(:respond_to?)
@@ -97,28 +101,28 @@ describe PgSearch::Multisearch::Rebuilder do
97
101
  end
98
102
  end
99
103
 
100
- expect(Model).not_to receive(:rebuild_pg_search_documents)
101
- rebuilder.rebuild
104
+ without_partial_double_verification do
105
+ allow(Model).to receive(:rebuild_pg_search_documents)
106
+ rebuilder.rebuild
107
+ expect(Model).not_to have_received(:rebuild_pg_search_documents)
108
+ end
102
109
  end
103
110
 
104
- it "should execute the default SQL" do
111
+ # rubocop:disable RSpec/ExampleLength
112
+ it "executes the default SQL" do
105
113
  time = Time.utc(2001, 1, 1, 0, 0, 0)
106
- rebuilder = PgSearch::Multisearch::Rebuilder.new(Model, -> { time })
107
-
108
- # Handle change in precision of Time objects in SQL in Active Record 4.0.1
109
- # https://github.com/rails/rails/commit/17f5d8e062909f1fcae25351834d8e89967b645e
110
- expected_timestamp = has_microsecond_precision? ? "2001-01-01 00:00:00.000000" : "2001-01-01 00:00:00"
111
-
112
- expected_sql = <<-SQL.strip_heredoc
113
- INSERT INTO "pg_search_documents" (searchable_type, searchable_id, content, created_at, updated_at)
114
- SELECT 'Model' AS searchable_type,
115
- #{Model.quoted_table_name}.#{Model.primary_key} AS searchable_id,
116
- (
117
- coalesce(#{Model.quoted_table_name}."name"::text, '')
118
- ) AS content,
119
- '#{expected_timestamp}' AS created_at,
120
- '#{expected_timestamp}' AS updated_at
121
- FROM #{Model.quoted_table_name}
114
+ rebuilder = described_class.new(Model, -> { time })
115
+
116
+ expected_sql = <<~SQL.squish
117
+ INSERT INTO "pg_search_documents" (searchable_type, searchable_id, content, created_at, updated_at)
118
+ SELECT 'Model' AS searchable_type,
119
+ #{Model.quoted_table_name}.#{Model.primary_key} AS searchable_id,
120
+ (
121
+ coalesce(#{Model.quoted_table_name}."name"::text, '')
122
+ ) AS content,
123
+ '2001-01-01 00:00:00' AS created_at,
124
+ '2001-01-01 00:00:00' AS updated_at
125
+ FROM #{Model.quoted_table_name}
122
126
  SQL
123
127
 
124
128
  executed_sql = []
@@ -133,56 +137,54 @@ describe PgSearch::Multisearch::Rebuilder do
133
137
  expect(executed_sql.length).to eq(1)
134
138
  expect(executed_sql.first.strip).to eq(expected_sql.strip)
135
139
  end
140
+ # rubocop:enable RSpec/ExampleLength
136
141
 
137
- context "for a model with a camel case column" do
142
+ context "with a model with a camel case column" do
138
143
  with_model :ModelWithCamelCaseColumn do
139
144
  table do |t|
140
145
  t.string :camelName
141
146
  end
142
147
 
143
148
  model do
144
- include PgSearch
149
+ include PgSearch::Model
145
150
  multisearchable against: :name
146
151
  end
147
152
  end
148
153
 
149
154
  it "creates search document without PG error" do
150
155
  time = Time.utc(2001, 1, 1, 0, 0, 0)
151
- rebuilder = PgSearch::Multisearch::Rebuilder.new(Model, -> { time })
156
+ rebuilder = described_class.new(Model, -> { time })
152
157
  rebuilder.rebuild
153
158
  end
154
159
  end
155
160
 
156
- context "for a model with a non-standard primary key" do
161
+ context "with a model with a non-standard primary key" do
157
162
  with_model :ModelWithNonStandardPrimaryKey do
158
163
  table primary_key: :non_standard_primary_key do |t|
159
164
  t.string :name
160
165
  end
161
166
 
162
167
  model do
163
- include PgSearch
168
+ include PgSearch::Model
164
169
  multisearchable against: :name
165
170
  end
166
171
  end
167
172
 
173
+ # rubocop:disable RSpec/ExampleLength
168
174
  it "generates SQL with the correct primary key" do
169
175
  time = Time.utc(2001, 1, 1, 0, 0, 0)
170
- rebuilder = PgSearch::Multisearch::Rebuilder.new(ModelWithNonStandardPrimaryKey, -> { time })
171
-
172
- # Handle change in precision of Time objects in SQL in Active Record 4.0.1
173
- # https://github.com/rails/rails/commit/17f5d8e062909f1fcae25351834d8e89967b645e
174
- expected_timestamp = has_microsecond_precision? ? "2001-01-01 00:00:00.000000" : "2001-01-01 00:00:00"
175
-
176
- expected_sql = <<-SQL.strip_heredoc
177
- INSERT INTO "pg_search_documents" (searchable_type, searchable_id, content, created_at, updated_at)
178
- SELECT 'ModelWithNonStandardPrimaryKey' AS searchable_type,
179
- #{ModelWithNonStandardPrimaryKey.quoted_table_name}.non_standard_primary_key AS searchable_id,
180
- (
181
- coalesce(#{ModelWithNonStandardPrimaryKey.quoted_table_name}."name"::text, '')
182
- ) AS content,
183
- '#{expected_timestamp}' AS created_at,
184
- '#{expected_timestamp}' AS updated_at
185
- FROM #{ModelWithNonStandardPrimaryKey.quoted_table_name}
176
+ rebuilder = described_class.new(ModelWithNonStandardPrimaryKey, -> { time })
177
+
178
+ expected_sql = <<~SQL.squish
179
+ INSERT INTO "pg_search_documents" (searchable_type, searchable_id, content, created_at, updated_at)
180
+ SELECT 'ModelWithNonStandardPrimaryKey' AS searchable_type,
181
+ #{ModelWithNonStandardPrimaryKey.quoted_table_name}.non_standard_primary_key AS searchable_id,
182
+ (
183
+ coalesce(#{ModelWithNonStandardPrimaryKey.quoted_table_name}."name"::text, '')
184
+ ) AS content,
185
+ '2001-01-01 00:00:00' AS created_at,
186
+ '2001-01-01 00:00:00' AS updated_at
187
+ FROM #{ModelWithNonStandardPrimaryKey.quoted_table_name}
186
188
  SQL
187
189
 
188
190
  executed_sql = []
@@ -197,6 +199,7 @@ describe PgSearch::Multisearch::Rebuilder do
197
199
  expect(executed_sql.length).to eq(1)
198
200
  expect(executed_sql.first.strip).to eq(expected_sql.strip)
199
201
  end
202
+ # rubocop:enable RSpec/ExampleLength
200
203
  end
201
204
  end
202
205
 
@@ -206,7 +209,7 @@ describe PgSearch::Multisearch::Rebuilder do
206
209
  end
207
210
 
208
211
  model do
209
- include PgSearch
212
+ include PgSearch::Model
210
213
  multisearchable against: [:foo]
211
214
 
212
215
  def foo
@@ -215,10 +218,11 @@ describe PgSearch::Multisearch::Rebuilder do
215
218
  end
216
219
  end
217
220
 
221
+ # rubocop:disable RSpec/ExampleLength
218
222
  it "calls update_pg_search_document on each record" do
219
223
  record = Model.create!
220
224
 
221
- rebuilder = PgSearch::Multisearch::Rebuilder.new(Model)
225
+ rebuilder = described_class.new(Model)
222
226
 
223
227
  # stub respond_to? to return false since should_not_receive defines the method
224
228
  original_respond_to = Model.method(:respond_to?)
@@ -229,16 +233,49 @@ describe PgSearch::Multisearch::Rebuilder do
229
233
  original_respond_to.call(method_name, *args)
230
234
  end
231
235
  end
232
- expect(Model).not_to receive(:rebuild_pg_search_documents)
233
236
 
234
- rebuilder.rebuild
237
+ without_partial_double_verification do
238
+ allow(Model).to receive(:rebuild_pg_search_documents)
239
+
240
+ rebuilder.rebuild
241
+
242
+ expect(Model).not_to have_received(:rebuild_pg_search_documents)
243
+ end
235
244
 
236
245
  expect(record.pg_search_document).to be_present
237
246
  end
247
+ # rubocop:enable RSpec/ExampleLength
248
+ end
249
+
250
+ context "when only additional_attributes is set" do
251
+ with_model :Model do
252
+ table do |t|
253
+ t.string :name
254
+ end
255
+
256
+ model do
257
+ include PgSearch::Model
258
+ multisearchable against: :name,
259
+ additional_attributes: ->(obj) { { additional_attribute_column: "#{obj.class}::#{obj.id}" } }
260
+ end
261
+ end
262
+
263
+ it "calls update_pg_search_document on each record" do
264
+ record_1 = Model.create!(name: "record_1", id: 1)
265
+ record_2 = Model.create!(name: "record_2", id: 2)
266
+
267
+ PgSearch::Document.delete_all
268
+
269
+ rebuilder = described_class.new(Model)
270
+ rebuilder.rebuild
271
+
272
+ expect(record_1.reload.pg_search_document.additional_attribute_column).to eq("Model::1")
273
+ expect(record_2.reload.pg_search_document.additional_attribute_column).to eq("Model::2")
274
+ end
238
275
  end
239
276
  end
240
277
 
241
- context "and multisearchable is conditional" do
278
+ context "when multisearchable is conditional" do
242
279
  context "via :if" do
243
280
  with_model :Model do
244
281
  table do |t|
@@ -246,16 +283,17 @@ describe PgSearch::Multisearch::Rebuilder do
246
283
  end
247
284
 
248
285
  model do
249
- include PgSearch
286
+ include PgSearch::Model
250
287
  multisearchable if: :active?
251
288
  end
252
289
  end
253
290
 
291
+ # rubocop:disable RSpec/ExampleLength
254
292
  it "calls update_pg_search_document on each record" do
255
293
  record_1 = Model.create!(active: true)
256
294
  record_2 = Model.create!(active: false)
257
295
 
258
- rebuilder = PgSearch::Multisearch::Rebuilder.new(Model)
296
+ rebuilder = described_class.new(Model)
259
297
 
260
298
  # stub respond_to? to return false since should_not_receive defines the method
261
299
  original_respond_to = Model.method(:respond_to?)
@@ -266,13 +304,17 @@ describe PgSearch::Multisearch::Rebuilder do
266
304
  original_respond_to.call(method_name, *args)
267
305
  end
268
306
  end
269
- expect(Model).not_to receive(:rebuild_pg_search_documents)
270
307
 
271
- rebuilder.rebuild
308
+ without_partial_double_verification do
309
+ allow(Model).to receive(:rebuild_pg_search_documents)
310
+ rebuilder.rebuild
311
+ expect(Model).not_to have_received(:rebuild_pg_search_documents)
312
+ end
272
313
 
273
314
  expect(record_1.pg_search_document).to be_present
274
315
  expect(record_2.pg_search_document).not_to be_present
275
316
  end
317
+ # rubocop:enable RSpec/ExampleLength
276
318
  end
277
319
 
278
320
  context "via :unless" do
@@ -282,16 +324,17 @@ describe PgSearch::Multisearch::Rebuilder do
282
324
  end
283
325
 
284
326
  model do
285
- include PgSearch
327
+ include PgSearch::Model
286
328
  multisearchable unless: :inactive?
287
329
  end
288
330
  end
289
331
 
332
+ # rubocop:disable RSpec/ExampleLength
290
333
  it "calls update_pg_search_document on each record" do
291
334
  record_1 = Model.create!(inactive: true)
292
335
  record_2 = Model.create!(inactive: false)
293
336
 
294
- rebuilder = PgSearch::Multisearch::Rebuilder.new(Model)
337
+ rebuilder = described_class.new(Model)
295
338
 
296
339
  # stub respond_to? to return false since should_not_receive defines the method
297
340
  original_respond_to = Model.method(:respond_to?)
@@ -302,15 +345,20 @@ describe PgSearch::Multisearch::Rebuilder do
302
345
  original_respond_to.call(method_name, *args)
303
346
  end
304
347
  end
305
- expect(Model).not_to receive(:rebuild_pg_search_documents)
306
348
 
307
- rebuilder.rebuild
349
+ without_partial_double_verification do
350
+ allow(Model).to receive(:rebuild_pg_search_documents)
351
+ rebuilder.rebuild
352
+ expect(Model).not_to have_received(:rebuild_pg_search_documents)
353
+ end
308
354
 
309
355
  expect(record_1.pg_search_document).not_to be_present
310
356
  expect(record_2.pg_search_document).to be_present
311
357
  end
358
+ # rubocop:enable RSpec/ExampleLength
312
359
  end
313
360
  end
314
361
  end
315
362
  end
316
363
  end
364
+ # rubocop:enable RSpec/NestedGroups