pg_search 2.1.2 → 2.3.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. checksums.yaml +5 -5
  2. data/.codeclimate.yml +1 -0
  3. data/.editorconfig +10 -0
  4. data/.github/dependabot.yml +11 -0
  5. data/.github/workflows/ci.yml +75 -0
  6. data/.jrubyrc +1 -0
  7. data/.rubocop.yml +95 -10
  8. data/.travis.yml +26 -48
  9. data/CHANGELOG.md +179 -112
  10. data/CODE_OF_CONDUCT.md +76 -0
  11. data/CONTRIBUTING.md +5 -3
  12. data/Gemfile +6 -4
  13. data/LICENSE +1 -1
  14. data/README.md +307 -198
  15. data/Rakefile +7 -3
  16. data/lib/pg_search/configuration/association.rb +2 -0
  17. data/lib/pg_search/configuration/column.rb +2 -0
  18. data/lib/pg_search/configuration/foreign_column.rb +2 -0
  19. data/lib/pg_search/configuration.rb +20 -6
  20. data/lib/pg_search/document.rb +7 -5
  21. data/lib/pg_search/features/dmetaphone.rb +7 -7
  22. data/lib/pg_search/features/feature.rb +4 -2
  23. data/lib/pg_search/features/trigram.rb +31 -5
  24. data/lib/pg_search/features/tsearch.rb +18 -14
  25. data/lib/pg_search/features.rb +2 -0
  26. data/lib/pg_search/migration/dmetaphone_generator.rb +3 -1
  27. data/lib/pg_search/migration/generator.rb +4 -2
  28. data/lib/pg_search/migration/multisearch_generator.rb +2 -1
  29. data/lib/pg_search/migration/templates/add_pg_search_dmetaphone_support_functions.rb.erb +6 -6
  30. data/lib/pg_search/migration/templates/create_pg_search_documents.rb.erb +3 -3
  31. data/lib/pg_search/model.rb +57 -0
  32. data/lib/pg_search/multisearch/rebuilder.rb +14 -6
  33. data/lib/pg_search/multisearch.rb +23 -6
  34. data/lib/pg_search/multisearchable.rb +10 -6
  35. data/lib/pg_search/normalizer.rb +2 -0
  36. data/lib/pg_search/railtie.rb +2 -0
  37. data/lib/pg_search/scope_options.rb +26 -49
  38. data/lib/pg_search/tasks.rb +5 -1
  39. data/lib/pg_search/version.rb +3 -1
  40. data/lib/pg_search.rb +17 -55
  41. data/pg_search.gemspec +19 -11
  42. data/spec/.rubocop.yml +2 -2
  43. data/spec/integration/.rubocop.yml +11 -0
  44. data/spec/integration/associations_spec.rb +125 -162
  45. data/spec/integration/deprecation_spec.rb +33 -0
  46. data/spec/integration/pagination_spec.rb +10 -8
  47. data/spec/integration/pg_search_spec.rb +359 -306
  48. data/spec/integration/single_table_inheritance_spec.rb +18 -17
  49. data/spec/lib/pg_search/configuration/association_spec.rb +17 -13
  50. data/spec/lib/pg_search/configuration/column_spec.rb +2 -0
  51. data/spec/lib/pg_search/configuration/foreign_column_spec.rb +6 -4
  52. data/spec/lib/pg_search/features/dmetaphone_spec.rb +6 -4
  53. data/spec/lib/pg_search/features/trigram_spec.rb +51 -20
  54. data/spec/lib/pg_search/features/tsearch_spec.rb +29 -21
  55. data/spec/lib/pg_search/multisearch/rebuilder_spec.rb +151 -85
  56. data/spec/lib/pg_search/multisearch_spec.rb +67 -37
  57. data/spec/lib/pg_search/multisearchable_spec.rb +217 -123
  58. data/spec/lib/pg_search/normalizer_spec.rb +14 -10
  59. data/spec/lib/pg_search_spec.rb +102 -89
  60. data/spec/spec_helper.rb +25 -6
  61. data/spec/support/database.rb +19 -21
  62. data/spec/support/with_model.rb +2 -0
  63. metadata +106 -29
  64. data/.autotest +0 -5
  65. data/.rubocop_todo.yml +0 -163
  66. data/Guardfile +0 -6
@@ -1,5 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "spec_helper"
2
4
 
5
+ # rubocop:disable RSpec/NestedGroups
3
6
  describe "an Active Record model which includes PgSearch" do
4
7
  with_model :ModelWithPgSearch do
5
8
  table do |t|
@@ -10,7 +13,7 @@ describe "an Active Record model which includes PgSearch" do
10
13
  end
11
14
 
12
15
  model do
13
- include PgSearch
16
+ include PgSearch::Model
14
17
  belongs_to :parent_model
15
18
  end
16
19
  end
@@ -20,7 +23,7 @@ describe "an Active Record model which includes PgSearch" do
20
23
  end
21
24
 
22
25
  model do
23
- include PgSearch
26
+ include PgSearch::Model
24
27
  has_many :models_with_pg_search
25
28
  scope :active, -> { where(active: true) }
26
29
  end
@@ -28,7 +31,7 @@ describe "an Active Record model which includes PgSearch" do
28
31
 
29
32
  describe ".pg_search_scope" do
30
33
  it "builds a chainable scope" do
31
- ModelWithPgSearch.pg_search_scope "matching_query", :against => []
34
+ ModelWithPgSearch.pg_search_scope "matching_query", against: []
32
35
  scope = ModelWithPgSearch.where("1 = 1").matching_query("foo").where("1 = 1")
33
36
  expect(scope).to be_an ActiveRecord::Relation
34
37
  end
@@ -36,15 +39,15 @@ describe "an Active Record model which includes PgSearch" do
36
39
  context "when passed a lambda" do
37
40
  it "builds a dynamic scope" do
38
41
  ModelWithPgSearch.pg_search_scope :search_title_or_content,
39
- lambda { |query, pick_content|
40
- {
41
- :query => query.gsub("-remove-", ""),
42
- :against => pick_content ? :content : :title
43
- }
44
- }
42
+ lambda { |query, pick_content|
43
+ {
44
+ query: query.gsub("-remove-", ""),
45
+ against: pick_content ? :content : :title
46
+ }
47
+ }
45
48
 
46
- included = ModelWithPgSearch.create!(:title => 'foo', :content => 'bar')
47
- excluded = ModelWithPgSearch.create!(:title => 'bar', :content => 'foo')
49
+ included = ModelWithPgSearch.create!(title: 'foo', content: 'bar')
50
+ excluded = ModelWithPgSearch.create!(title: 'bar', content: 'foo')
48
51
 
49
52
  expect(ModelWithPgSearch.search_title_or_content('fo-remove-o', false)).to eq([included])
50
53
  expect(ModelWithPgSearch.search_title_or_content('b-remove-ar', true)).to eq([included])
@@ -54,18 +57,18 @@ describe "an Active Record model which includes PgSearch" do
54
57
  context "when an unknown option is passed in" do
55
58
  it "raises an exception when invoked" do
56
59
  ModelWithPgSearch.pg_search_scope :with_unknown_option,
57
- :against => :content,
58
- :foo => :bar
60
+ against: :content,
61
+ foo: :bar
59
62
 
60
63
  expect {
61
64
  ModelWithPgSearch.with_unknown_option("foo")
62
65
  }.to raise_error(ArgumentError, /foo/)
63
66
  end
64
67
 
65
- context "dynamically" do
68
+ context "with a lambda" do
66
69
  it "raises an exception when invoked" do
67
70
  ModelWithPgSearch.pg_search_scope :with_unknown_option,
68
- ->(*) { {:against => :content, :foo => :bar} }
71
+ ->(*) { { against: :content, foo: :bar } }
69
72
 
70
73
  expect {
71
74
  ModelWithPgSearch.with_unknown_option("foo")
@@ -77,18 +80,18 @@ describe "an Active Record model which includes PgSearch" do
77
80
  context "when an unknown :using is passed" do
78
81
  it "raises an exception when invoked" do
79
82
  ModelWithPgSearch.pg_search_scope :with_unknown_using,
80
- :against => :content,
81
- :using => :foo
83
+ against: :content,
84
+ using: :foo
82
85
 
83
86
  expect {
84
87
  ModelWithPgSearch.with_unknown_using("foo")
85
88
  }.to raise_error(ArgumentError, /foo/)
86
89
  end
87
90
 
88
- context "dynamically" do
91
+ context "with a lambda" do
89
92
  it "raises an exception when invoked" do
90
93
  ModelWithPgSearch.pg_search_scope :with_unknown_using,
91
- ->(*) { {:against => :content, :using => :foo} }
94
+ ->(*) { { against: :content, using: :foo } }
92
95
 
93
96
  expect {
94
97
  ModelWithPgSearch.with_unknown_using("foo")
@@ -100,18 +103,18 @@ describe "an Active Record model which includes PgSearch" do
100
103
  context "when an unknown :ignoring is passed" do
101
104
  it "raises an exception when invoked" do
102
105
  ModelWithPgSearch.pg_search_scope :with_unknown_ignoring,
103
- :against => :content,
104
- :ignoring => :foo
106
+ against: :content,
107
+ ignoring: :foo
105
108
 
106
109
  expect {
107
110
  ModelWithPgSearch.with_unknown_ignoring("foo")
108
111
  }.to raise_error(ArgumentError, /ignoring.*foo/)
109
112
  end
110
113
 
111
- context "dynamically" do
114
+ context "with a lambda" do
112
115
  it "raises an exception when invoked" do
113
116
  ModelWithPgSearch.pg_search_scope :with_unknown_ignoring,
114
- ->(*) { {:against => :content, :ignoring => :foo} }
117
+ ->(*) { { against: :content, ignoring: :foo } }
115
118
 
116
119
  expect {
117
120
  ModelWithPgSearch.with_unknown_ignoring("foo")
@@ -128,23 +131,39 @@ describe "an Active Record model which includes PgSearch" do
128
131
  }.to raise_error(ArgumentError, /against/)
129
132
  end
130
133
 
131
- context "dynamically" do
134
+ context "with a lambda" do
132
135
  it "raises an exception when invoked" do
133
- ModelWithPgSearch.pg_search_scope :with_unknown_ignoring, ->(*){ {} }
136
+ ModelWithPgSearch.pg_search_scope :with_unknown_ignoring, ->(*) { {} }
134
137
 
135
138
  expect {
136
139
  ModelWithPgSearch.with_unknown_ignoring("foo")
137
140
  }.to raise_error(ArgumentError, /against/)
138
141
  end
139
142
  end
143
+
144
+ context "when a tsvector column is specified" do
145
+ it "does not raise an exception when invoked" do
146
+ ModelWithPgSearch.pg_search_scope :with_unknown_ignoring, {
147
+ using: {
148
+ tsearch: {
149
+ tsvector_column: "tsv"
150
+ }
151
+ }
152
+ }
153
+
154
+ expect {
155
+ ModelWithPgSearch.with_unknown_ignoring("foo")
156
+ }.not_to raise_error
157
+ end
158
+ end
140
159
  end
141
160
  end
142
161
  end
143
162
 
144
163
  describe "a search scope" do
145
- context "against a single column" do
164
+ context "when against a single column" do
146
165
  before do
147
- ModelWithPgSearch.pg_search_scope :search_content, :against => :content
166
+ ModelWithPgSearch.pg_search_scope :search_content, against: :content
148
167
  end
149
168
 
150
169
  context "when chained after a select() scope" do
@@ -155,7 +174,7 @@ describe "an Active Record model which includes PgSearch" do
155
174
  results = ModelWithPgSearch.select('id, title').search_content('foo')
156
175
 
157
176
  expect(results).to include(included)
158
- expect(results).to_not include(excluded)
177
+ expect(results).not_to include(excluded)
159
178
 
160
179
  expect(results.first.attributes.key?('content')).to eq false
161
180
 
@@ -172,7 +191,7 @@ describe "an Active Record model which includes PgSearch" do
172
191
  results = ModelWithPgSearch.search_content('foo').select('id, title')
173
192
 
174
193
  expect(results).to include(included)
175
- expect(results).to_not include(excluded)
194
+ expect(results).not_to include(excluded)
176
195
 
177
196
  expect(results.first.attributes.key?('content')).to eq false
178
197
 
@@ -189,7 +208,7 @@ describe "an Active Record model which includes PgSearch" do
189
208
  results = ModelWithPgSearch.select('id').search_content('foo').select('title')
190
209
 
191
210
  expect(results).to include(included)
192
- expect(results).to_not include(excluded)
211
+ expect(results).not_to include(excluded)
193
212
 
194
213
  expect(results.first.attributes.key?('content')).to eq false
195
214
 
@@ -198,7 +217,7 @@ describe "an Active Record model which includes PgSearch" do
198
217
  end
199
218
  end
200
219
 
201
- context "chained to a cross-table scope" do
220
+ context "when chained to a cross-table scope" do
202
221
  with_model :House do
203
222
  table do |t|
204
223
  t.references :person
@@ -206,7 +225,7 @@ describe "an Active Record model which includes PgSearch" do
206
225
  end
207
226
 
208
227
  model do
209
- include PgSearch
228
+ include PgSearch::Model
210
229
  belongs_to :person
211
230
  pg_search_scope :search_city, against: [:city]
212
231
  end
@@ -218,11 +237,11 @@ describe "an Active Record model which includes PgSearch" do
218
237
  end
219
238
 
220
239
  model do
221
- include PgSearch
240
+ include PgSearch::Model
222
241
  has_many :houses
223
242
  pg_search_scope :named, against: [:name]
224
243
  scope :with_house_in_city, lambda { |city|
225
- joins(:houses).where(House.table_name.to_sym => {city: city})
244
+ joins(:houses).where(House.table_name.to_sym => { city: city })
226
245
  }
227
246
  scope :house_search_city, lambda { |query|
228
247
  joins(:houses).merge(House.search_city(query))
@@ -268,7 +287,7 @@ describe "an Active Record model which includes PgSearch" do
268
287
  it "does not raise an exception" do
269
288
  relation = Person.named('foo').house_search_city('bar')
270
289
 
271
- expect { relation.to_a }.to_not raise_error
290
+ expect { relation.to_a }.not_to raise_error
272
291
  end
273
292
  end
274
293
  end
@@ -281,20 +300,20 @@ describe "an Active Record model which includes PgSearch" do
281
300
  it "does not raise an exception" do
282
301
  relation = ModelWithPgSearch.search_content('foo').search_title('bar')
283
302
 
284
- expect { relation.to_a }.to_not raise_error
303
+ expect { relation.to_a }.not_to raise_error
285
304
  end
286
305
  end
287
306
 
288
307
  it "returns an empty array when a blank query is passed in" do
289
- ModelWithPgSearch.create!(:content => 'foo')
308
+ ModelWithPgSearch.create!(content: 'foo')
290
309
 
291
310
  results = ModelWithPgSearch.search_content('')
292
311
  expect(results).to eq([])
293
312
  end
294
313
 
295
314
  it "returns rows where the column contains the term in the query" do
296
- included = ModelWithPgSearch.create!(:content => 'foo')
297
- excluded = ModelWithPgSearch.create!(:content => 'bar')
315
+ included = ModelWithPgSearch.create!(content: 'foo')
316
+ excluded = ModelWithPgSearch.create!(content: 'bar')
298
317
 
299
318
  results = ModelWithPgSearch.search_content('foo')
300
319
  expect(results).to include(included)
@@ -302,24 +321,24 @@ describe "an Active Record model which includes PgSearch" do
302
321
  end
303
322
 
304
323
  it "returns the correct count" do
305
- ModelWithPgSearch.create!(:content => 'foo')
306
- ModelWithPgSearch.create!(:content => 'bar')
324
+ ModelWithPgSearch.create!(content: 'foo')
325
+ ModelWithPgSearch.create!(content: 'bar')
307
326
 
308
327
  results = ModelWithPgSearch.search_content('foo')
309
328
  expect(results.count).to eq 1
310
329
  end
311
330
 
312
331
  it "returns the correct count(:all)" do
313
- ModelWithPgSearch.create!(:content => 'foo')
314
- ModelWithPgSearch.create!(:content => 'bar')
332
+ ModelWithPgSearch.create!(content: 'foo')
333
+ ModelWithPgSearch.create!(content: 'bar')
315
334
 
316
335
  results = ModelWithPgSearch.search_content('foo')
317
336
  expect(results.count(:all)).to eq 1
318
337
  end
319
338
 
320
339
  it "supports #select" do
321
- record = ModelWithPgSearch.create!(:content => 'foo')
322
- other_record = ModelWithPgSearch.create!(:content => 'bar')
340
+ record = ModelWithPgSearch.create!(content: 'foo')
341
+ other_record = ModelWithPgSearch.create!(content: 'bar')
323
342
 
324
343
  records_with_only_id = ModelWithPgSearch.search_content('foo').select('id')
325
344
  expect(records_with_only_id.length).to eq 1
@@ -330,27 +349,27 @@ describe "an Active Record model which includes PgSearch" do
330
349
  end
331
350
 
332
351
  it "supports #pluck" do
333
- record = ModelWithPgSearch.create!(:content => 'foo')
334
- other_record = ModelWithPgSearch.create!(:content => 'bar')
352
+ record = ModelWithPgSearch.create!(content: 'foo')
353
+ other_record = ModelWithPgSearch.create!(content: 'bar')
335
354
 
336
355
  ids = ModelWithPgSearch.search_content('foo').pluck('id')
337
356
  expect(ids).to eq [record.id]
338
357
  end
339
358
 
340
359
  it "supports adding where clauses using the pg_search.rank" do
341
- once = ModelWithPgSearch.create!(:content => 'foo bar')
342
- twice = ModelWithPgSearch.create!(:content => 'foo foo')
360
+ once = ModelWithPgSearch.create!(content: 'foo bar')
361
+ twice = ModelWithPgSearch.create!(content: 'foo foo')
343
362
 
344
363
  records = ModelWithPgSearch.search_content('foo')
345
- .where("#{PgSearch::Configuration.alias(ModelWithPgSearch.table_name)}.rank > 0.07")
364
+ .where("#{PgSearch::Configuration.alias(ModelWithPgSearch.table_name)}.rank > 0.07")
346
365
 
347
366
  expect(records).to eq [twice]
348
367
  end
349
368
 
350
369
  it "returns rows where the column contains all the terms in the query in any order" do
351
- included = [ModelWithPgSearch.create!(:content => 'foo bar'),
352
- ModelWithPgSearch.create!(:content => 'bar foo')]
353
- excluded = ModelWithPgSearch.create!(:content => 'foo')
370
+ included = [ModelWithPgSearch.create!(content: 'foo bar'),
371
+ ModelWithPgSearch.create!(content: 'bar foo')]
372
+ excluded = ModelWithPgSearch.create!(content: 'foo')
354
373
 
355
374
  results = ModelWithPgSearch.search_content('foo bar')
356
375
  expect(results).to match_array(included)
@@ -358,8 +377,8 @@ describe "an Active Record model which includes PgSearch" do
358
377
  end
359
378
 
360
379
  it "returns rows that match the query but not its case" do
361
- included = [ModelWithPgSearch.create!(:content => "foo"),
362
- ModelWithPgSearch.create!(:content => "FOO")]
380
+ included = [ModelWithPgSearch.create!(content: "foo"),
381
+ ModelWithPgSearch.create!(content: "FOO")]
363
382
 
364
383
  results = ModelWithPgSearch.search_content("Foo")
365
384
  expect(results).to match_array(included)
@@ -369,8 +388,8 @@ describe "an Active Record model which includes PgSearch" do
369
388
  # \303\241 is a with acute accent
370
389
  # \303\251 is e with acute accent
371
390
 
372
- included = ModelWithPgSearch.create!(:content => "abcd\303\251f")
373
- excluded = ModelWithPgSearch.create!(:content => "\303\241bcdef")
391
+ included = ModelWithPgSearch.create!(content: "abcd\303\251f")
392
+ excluded = ModelWithPgSearch.create!(content: "\303\241bcdef")
374
393
 
375
394
  results = ModelWithPgSearch.search_content("abcd\303\251f")
376
395
  expect(results).to eq([included])
@@ -378,8 +397,8 @@ describe "an Active Record model which includes PgSearch" do
378
397
  end
379
398
 
380
399
  it "returns rows that match the query but not rows that are prefixed by the query" do
381
- included = ModelWithPgSearch.create!(:content => 'pre')
382
- excluded = ModelWithPgSearch.create!(:content => 'prefix')
400
+ included = ModelWithPgSearch.create!(content: 'pre')
401
+ excluded = ModelWithPgSearch.create!(content: 'prefix')
383
402
 
384
403
  results = ModelWithPgSearch.search_content("pre")
385
404
  expect(results).to eq([included])
@@ -387,28 +406,37 @@ describe "an Active Record model which includes PgSearch" do
387
406
  end
388
407
 
389
408
  it "returns rows that match the query exactly and not those that match the query when stemmed by the default english dictionary" do
390
- included = ModelWithPgSearch.create!(:content => "jumped")
391
- excluded = [ModelWithPgSearch.create!(:content => "jump"),
392
- ModelWithPgSearch.create!(:content => "jumping")]
409
+ included = ModelWithPgSearch.create!(content: "jumped")
410
+ excluded = [ModelWithPgSearch.create!(content: "jump"),
411
+ ModelWithPgSearch.create!(content: "jumping")]
393
412
 
394
413
  results = ModelWithPgSearch.search_content("jumped")
395
414
  expect(results).to eq([included])
396
415
  end
397
416
 
398
417
  it "returns rows that match sorted by rank" do
399
- loser = ModelWithPgSearch.create!(:content => 'foo')
400
- winner = ModelWithPgSearch.create!(:content => 'foo foo')
418
+ loser = ModelWithPgSearch.create!(content: 'foo')
419
+ winner = ModelWithPgSearch.create!(content: 'foo foo')
401
420
 
402
421
  results = ModelWithPgSearch.search_content("foo").with_pg_search_rank
403
422
  expect(results[0].pg_search_rank).to be > results[1].pg_search_rank
404
423
  expect(results).to eq([winner, loser])
405
424
  end
406
425
 
426
+ it 'preserves column selection when with_pg_search_rank is chained after a select()' do
427
+ loser = ModelWithPgSearch.create!(title: 'foo', content: 'bar')
428
+
429
+ results = ModelWithPgSearch.search_content('bar').select(:content).with_pg_search_rank
430
+
431
+ expect(results.length).to be 1
432
+ expect(results.first.as_json.keys).to contain_exactly('id', 'content', 'pg_search_rank')
433
+ end
434
+
407
435
  it 'allows pg_search_rank along with a join' do
408
436
  parent_1 = ParentModel.create!(id: 98)
409
437
  parent_2 = ParentModel.create!(id: 99)
410
- loser = ModelWithPgSearch.create!(:content => 'foo', parent_model: parent_2)
411
- winner = ModelWithPgSearch.create!(:content => 'foo foo', parent_model: parent_1)
438
+ loser = ModelWithPgSearch.create!(content: 'foo', parent_model: parent_2)
439
+ winner = ModelWithPgSearch.create!(content: 'foo foo', parent_model: parent_1)
412
440
 
413
441
  results = ModelWithPgSearch.joins(:parent_model).merge(ParentModel.active).search_content("foo").with_pg_search_rank
414
442
  expect(results.map(&:id)).to eq [winner.id, loser.id]
@@ -417,8 +445,8 @@ describe "an Active Record model which includes PgSearch" do
417
445
  end
418
446
 
419
447
  it "returns results that match sorted by primary key for records that rank the same" do
420
- sorted_results = [ModelWithPgSearch.create!(:content => 'foo'),
421
- ModelWithPgSearch.create!(:content => 'foo')].sort_by(&:id)
448
+ sorted_results = [ModelWithPgSearch.create!(content: 'foo'),
449
+ ModelWithPgSearch.create!(content: 'foo')].sort_by(&:id)
422
450
 
423
451
  results = ModelWithPgSearch.search_content("foo")
424
452
  expect(results).to eq(sorted_results)
@@ -426,13 +454,13 @@ describe "an Active Record model which includes PgSearch" do
426
454
 
427
455
  it "returns results that match a query with multiple space-separated search terms" do
428
456
  included = [
429
- ModelWithPgSearch.create!(:content => 'foo bar'),
430
- ModelWithPgSearch.create!(:content => 'bar foo'),
431
- ModelWithPgSearch.create!(:content => 'bar foo baz'),
457
+ ModelWithPgSearch.create!(content: 'foo bar'),
458
+ ModelWithPgSearch.create!(content: 'bar foo'),
459
+ ModelWithPgSearch.create!(content: 'bar foo baz')
432
460
  ]
433
461
  excluded = [
434
- ModelWithPgSearch.create!(:content => 'foo'),
435
- ModelWithPgSearch.create!(:content => 'foo baz')
462
+ ModelWithPgSearch.create!(content: 'foo'),
463
+ ModelWithPgSearch.create!(content: 'foo baz')
436
464
  ]
437
465
 
438
466
  results = ModelWithPgSearch.search_content('foo bar')
@@ -441,15 +469,15 @@ describe "an Active Record model which includes PgSearch" do
441
469
  end
442
470
 
443
471
  it "returns rows that match a query with characters that are invalid in a tsquery expression" do
444
- included = ModelWithPgSearch.create!(:content => "(:Foo.) Bar?, \\")
472
+ included = ModelWithPgSearch.create!(content: "(:Foo.) Bar?, \\")
445
473
 
446
474
  results = ModelWithPgSearch.search_content("foo :bar .,?() \\")
447
475
  expect(results).to eq([included])
448
476
  end
449
477
 
450
478
  it "accepts non-string queries and calls #to_s on them" do
451
- foo = ModelWithPgSearch.create!(:content => "foo")
452
- not_a_string = double(:to_s => "foo")
479
+ foo = ModelWithPgSearch.create!(content: "foo")
480
+ not_a_string = instance_double("Object", to_s: "foo")
453
481
  expect(ModelWithPgSearch.search_content(not_a_string)).to eq([foo])
454
482
  end
455
483
 
@@ -460,12 +488,12 @@ describe "an Active Record model which includes PgSearch" do
460
488
  end
461
489
 
462
490
  model do
463
- include PgSearch
491
+ include PgSearch::Model
464
492
 
465
493
  # WARNING: searching timestamps is not something PostgreSQL
466
494
  # full-text search is good at. Use at your own risk.
467
495
  pg_search_scope :search_timestamps,
468
- :against => %i[created_at updated_at]
496
+ against: %i[created_at updated_at]
469
497
  end
470
498
  end
471
499
 
@@ -479,19 +507,19 @@ describe "an Active Record model which includes PgSearch" do
479
507
  end
480
508
  end
481
509
 
482
- context "against multiple columns" do
510
+ context "when against multiple columns" do
483
511
  before do
484
- ModelWithPgSearch.pg_search_scope :search_title_and_content, :against => %i[title content]
512
+ ModelWithPgSearch.pg_search_scope :search_title_and_content, against: %i[title content]
485
513
  end
486
514
 
487
515
  it "returns rows whose columns contain all of the terms in the query across columns" do
488
516
  included = [
489
- ModelWithPgSearch.create!(:title => 'foo', :content => 'bar'),
490
- ModelWithPgSearch.create!(:title => 'bar', :content => 'foo')
517
+ ModelWithPgSearch.create!(title: 'foo', content: 'bar'),
518
+ ModelWithPgSearch.create!(title: 'bar', content: 'foo')
491
519
  ]
492
520
  excluded = [
493
- ModelWithPgSearch.create!(:title => 'foo', :content => 'foo'),
494
- ModelWithPgSearch.create!(:title => 'bar', :content => 'bar')
521
+ ModelWithPgSearch.create!(title: 'foo', content: 'foo'),
522
+ ModelWithPgSearch.create!(title: 'bar', content: 'bar')
495
523
  ]
496
524
 
497
525
  results = ModelWithPgSearch.search_title_and_content('foo bar')
@@ -503,8 +531,8 @@ describe "an Active Record model which includes PgSearch" do
503
531
  end
504
532
 
505
533
  it "returns rows where at one column contains all of the terms in the query and another does not" do
506
- in_title = ModelWithPgSearch.create!(:title => 'foo', :content => 'bar')
507
- in_content = ModelWithPgSearch.create!(:title => 'bar', :content => 'foo')
534
+ in_title = ModelWithPgSearch.create!(title: 'foo', content: 'bar')
535
+ in_content = ModelWithPgSearch.create!(title: 'bar', content: 'foo')
508
536
 
509
537
  results = ModelWithPgSearch.search_title_and_content('foo')
510
538
  expect(results).to match_array([in_title, in_content])
@@ -512,39 +540,39 @@ describe "an Active Record model which includes PgSearch" do
512
540
 
513
541
  # Searching with a NULL column will prevent any matches unless we coalesce it.
514
542
  it "returns rows where at one column contains all of the terms in the query and another is NULL" do
515
- included = ModelWithPgSearch.create!(:title => 'foo', :content => nil)
543
+ included = ModelWithPgSearch.create!(title: 'foo', content: nil)
516
544
  results = ModelWithPgSearch.search_title_and_content('foo')
517
545
  expect(results).to eq([included])
518
546
  end
519
547
  end
520
548
 
521
- context "using trigram" do
549
+ context "when using trigram" do
522
550
  before do
523
- ModelWithPgSearch.pg_search_scope :with_trigrams, :against => %i[title content], :using => :trigram
551
+ ModelWithPgSearch.pg_search_scope :with_trigrams, against: %i[title content], using: :trigram
524
552
  end
525
553
 
526
554
  it "returns rows where one searchable column and the query share enough trigrams" do
527
- included = ModelWithPgSearch.create!(:title => 'abcdefghijkl', :content => nil)
555
+ included = ModelWithPgSearch.create!(title: 'abcdefghijkl', content: nil)
528
556
  results = ModelWithPgSearch.with_trigrams('cdefhijkl')
529
557
  expect(results).to eq([included])
530
558
  end
531
559
 
532
560
  it "returns rows where multiple searchable columns and the query share enough trigrams" do
533
- included = ModelWithPgSearch.create!(:title => 'abcdef', :content => 'ghijkl')
561
+ included = ModelWithPgSearch.create!(title: 'abcdef', content: 'ghijkl')
534
562
  results = ModelWithPgSearch.with_trigrams('cdefhijkl')
535
563
  expect(results).to eq([included])
536
564
  end
537
565
 
538
566
  context "when a threshold is specified" do
539
567
  before do
540
- ModelWithPgSearch.pg_search_scope :with_strict_trigrams, :against => %i[title content], :using => {trigram: {threshold: 0.5}}
541
- ModelWithPgSearch.pg_search_scope :with_permissive_trigrams, :against => %i[title content], :using => {trigram: {threshold: 0.1}}
568
+ ModelWithPgSearch.pg_search_scope :with_strict_trigrams, against: %i[title content], using: { trigram: { threshold: 0.5 } }
569
+ ModelWithPgSearch.pg_search_scope :with_permissive_trigrams, against: %i[title content], using: { trigram: { threshold: 0.1 } }
542
570
  end
543
571
 
544
572
  it "uses the threshold in the trigram expression" do
545
- low_similarity = ModelWithPgSearch.create!(:title => "a")
546
- medium_similarity = ModelWithPgSearch.create!(:title => "abc")
547
- high_similarity = ModelWithPgSearch.create!(:title => "abcdefghijkl")
573
+ low_similarity = ModelWithPgSearch.create!(title: "a")
574
+ medium_similarity = ModelWithPgSearch.create!(title: "abc")
575
+ high_similarity = ModelWithPgSearch.create!(title: "abcdefghijkl")
548
576
 
549
577
  results = ModelWithPgSearch.with_strict_trigrams("abcdefg")
550
578
  expect(results).to include(high_similarity)
@@ -560,19 +588,19 @@ describe "an Active Record model which includes PgSearch" do
560
588
  end
561
589
  end
562
590
 
563
- context "using tsearch" do
591
+ context "when using tsearch" do
564
592
  before do
565
593
  ModelWithPgSearch.pg_search_scope :search_title_with_prefixes,
566
- :against => :title,
567
- :using => {
568
- :tsearch => {:prefix => true}
569
- }
594
+ against: :title,
595
+ using: {
596
+ tsearch: { prefix: true }
597
+ }
570
598
  end
571
599
 
572
- context "with :prefix => true" do
600
+ context "with prefix: true" do
573
601
  it "returns rows that match the query and that are prefixed by the query" do
574
- included = ModelWithPgSearch.create!(:title => 'prefix')
575
- excluded = ModelWithPgSearch.create!(:title => 'postfix')
602
+ included = ModelWithPgSearch.create!(title: 'prefix')
603
+ excluded = ModelWithPgSearch.create!(title: 'postfix')
576
604
 
577
605
  results = ModelWithPgSearch.search_title_with_prefixes("pre")
578
606
  expect(results).to eq([included])
@@ -580,8 +608,8 @@ describe "an Active Record model which includes PgSearch" do
580
608
  end
581
609
 
582
610
  it "returns rows that match the query when the query has a hyphen" do
583
- included = ModelWithPgSearch.create!(:title => 'foo-bar')
584
- excluded = ModelWithPgSearch.create!(:title => 'foo bar')
611
+ included = ModelWithPgSearch.create!(title: 'foo-bar')
612
+ excluded = ModelWithPgSearch.create!(title: 'foo bar')
585
613
 
586
614
  results = ModelWithPgSearch.search_title_with_prefixes("foo-bar")
587
615
  expect(results).to include(included)
@@ -592,16 +620,16 @@ describe "an Active Record model which includes PgSearch" do
592
620
  context "with the english dictionary" do
593
621
  before do
594
622
  ModelWithPgSearch.pg_search_scope :search_content_with_english,
595
- :against => :content,
596
- :using => {
597
- :tsearch => {:dictionary => :english}
598
- }
623
+ against: :content,
624
+ using: {
625
+ tsearch: { dictionary: :english }
626
+ }
599
627
  end
600
628
 
601
629
  it "returns rows that match the query when stemmed by the english dictionary" do
602
- included = [ModelWithPgSearch.create!(:content => "jump"),
603
- ModelWithPgSearch.create!(:content => "jumped"),
604
- ModelWithPgSearch.create!(:content => "jumping")]
630
+ included = [ModelWithPgSearch.create!(content: "jump"),
631
+ ModelWithPgSearch.create!(content: "jumped"),
632
+ ModelWithPgSearch.create!(content: "jumping")]
605
633
 
606
634
  results = ModelWithPgSearch.search_content_with_english("jump")
607
635
  expect(results).to match_array(included)
@@ -611,14 +639,14 @@ describe "an Active Record model which includes PgSearch" do
611
639
  describe "highlighting" do
612
640
  before do
613
641
  ["Strip Down", "Down", "Down and Out", "Won't Let You Down"].each do |name|
614
- ModelWithPgSearch.create! :content => name
642
+ ModelWithPgSearch.create! title: 'Just a title', content: name
615
643
  end
616
644
  end
617
645
 
618
646
  context "with highlight turned on" do
619
647
  before do
620
648
  ModelWithPgSearch.pg_search_scope :search_content,
621
- :against => :content
649
+ against: :content
622
650
  end
623
651
 
624
652
  it "adds a #pg_search_highlight method to each returned model record" do
@@ -632,26 +660,32 @@ describe "an Active Record model which includes PgSearch" do
632
660
 
633
661
  expect(result.pg_search_highlight).to eq("Won't <b>Let</b> You Down")
634
662
  end
663
+
664
+ it 'preserves column selection when with_pg_search_highlight is chained after a select()' do
665
+ result = ModelWithPgSearch.search_content("Let").select(:content).with_pg_search_highlight.first
666
+
667
+ expect(result.as_json.keys).to contain_exactly('id', 'content', 'pg_search_highlight')
668
+ end
635
669
  end
636
670
 
637
671
  context "with custom highlighting options" do
638
672
  before do
639
- ModelWithPgSearch.create! :content => "#{'text ' * 2}Let #{'text ' * 2}Let #{'text ' * 2}"
673
+ ModelWithPgSearch.create! content: "#{'text ' * 2}Let #{'text ' * 2}Let #{'text ' * 2}"
640
674
 
641
675
  ModelWithPgSearch.pg_search_scope :search_content,
642
- :against => :content,
643
- :using => {
644
- :tsearch => {
645
- :highlight => {
646
- :StartSel => '<mark class="highlight">',
647
- :StopSel => '</mark>',
648
- :FragmentDelimiter => '<delim class="my_delim">',
649
- :MaxFragments => 2,
650
- :MaxWords => 2,
651
- :MinWords => 1
652
- }
653
- }
654
- }
676
+ against: :content,
677
+ using: {
678
+ tsearch: {
679
+ highlight: {
680
+ StartSel: '<mark class="highlight">',
681
+ StopSel: '</mark>',
682
+ FragmentDelimiter: '<delim class="my_delim">',
683
+ MaxFragments: 2,
684
+ MaxWords: 2,
685
+ MinWords: 1
686
+ }
687
+ }
688
+ }
655
689
  end
656
690
 
657
691
  it "applies the options to the excerpts" do
@@ -665,12 +699,12 @@ describe "an Active Record model which includes PgSearch" do
665
699
  describe "ranking" do
666
700
  before do
667
701
  ["Strip Down", "Down", "Down and Out", "Won't Let You Down"].each do |name|
668
- ModelWithPgSearch.create! :content => name
702
+ ModelWithPgSearch.create! content: name
669
703
  end
670
704
  end
671
705
 
672
706
  it "adds a #pg_search_rank method to each returned model record" do
673
- ModelWithPgSearch.pg_search_scope :search_content, :against => :content
707
+ ModelWithPgSearch.pg_search_scope :search_content, against: :content
674
708
 
675
709
  result = ModelWithPgSearch.search_content("Strip Down").with_pg_search_rank.first
676
710
 
@@ -680,10 +714,10 @@ describe "an Active Record model which includes PgSearch" do
680
714
  context "with a normalization specified" do
681
715
  before do
682
716
  ModelWithPgSearch.pg_search_scope :search_content_with_normalization,
683
- :against => :content,
684
- :using => {
685
- :tsearch => {:normalization => 2}
686
- }
717
+ against: :content,
718
+ using: {
719
+ tsearch: { normalization: 2 }
720
+ }
687
721
  end
688
722
 
689
723
  it "ranks the results for documents with less text higher" do
@@ -697,8 +731,8 @@ describe "an Active Record model which includes PgSearch" do
697
731
  context "with no normalization" do
698
732
  before do
699
733
  ModelWithPgSearch.pg_search_scope :search_content_without_normalization,
700
- :against => :content,
701
- :using => :tsearch
734
+ against: :content,
735
+ using: :tsearch
702
736
  end
703
737
 
704
738
  it "ranks the results equally" do
@@ -710,15 +744,15 @@ describe "an Active Record model which includes PgSearch" do
710
744
  end
711
745
  end
712
746
 
713
- context "against columns ranked with arrays" do
747
+ context "when against columns ranked with arrays" do
714
748
  before do
715
749
  ModelWithPgSearch.pg_search_scope :search_weighted_by_array_of_arrays,
716
- :against => [[:content, 'B'], [:title, 'A']]
750
+ against: [[:content, 'B'], [:title, 'A']]
717
751
  end
718
752
 
719
753
  it "returns results sorted by weighted rank" do
720
- loser = ModelWithPgSearch.create!(:title => 'bar', :content => 'foo')
721
- winner = ModelWithPgSearch.create!(:title => 'foo', :content => 'bar')
754
+ loser = ModelWithPgSearch.create!(title: 'bar', content: 'foo')
755
+ winner = ModelWithPgSearch.create!(title: 'foo', content: 'bar')
722
756
 
723
757
  results = ModelWithPgSearch.search_weighted_by_array_of_arrays('foo').with_pg_search_rank
724
758
  expect(results[0].pg_search_rank).to be > results[1].pg_search_rank
@@ -726,15 +760,15 @@ describe "an Active Record model which includes PgSearch" do
726
760
  end
727
761
  end
728
762
 
729
- context "against columns ranked with a hash" do
763
+ context "when against columns ranked with a hash" do
730
764
  before do
731
765
  ModelWithPgSearch.pg_search_scope :search_weighted_by_hash,
732
- :against => {:content => 'B', :title => 'A'}
766
+ against: { content: 'B', title: 'A' }
733
767
  end
734
768
 
735
769
  it "returns results sorted by weighted rank" do
736
- loser = ModelWithPgSearch.create!(:title => 'bar', :content => 'foo')
737
- winner = ModelWithPgSearch.create!(:title => 'foo', :content => 'bar')
770
+ loser = ModelWithPgSearch.create!(title: 'bar', content: 'foo')
771
+ winner = ModelWithPgSearch.create!(title: 'foo', content: 'bar')
738
772
 
739
773
  results = ModelWithPgSearch.search_weighted_by_hash('foo').with_pg_search_rank
740
774
  expect(results[0].pg_search_rank).to be > results[1].pg_search_rank
@@ -742,15 +776,15 @@ describe "an Active Record model which includes PgSearch" do
742
776
  end
743
777
  end
744
778
 
745
- context "against columns of which only some are ranked" do
779
+ context "when against columns of which only some are ranked" do
746
780
  before do
747
781
  ModelWithPgSearch.pg_search_scope :search_weighted,
748
- :against => [:content, [:title, 'A']]
782
+ against: [:content, [:title, 'A']]
749
783
  end
750
784
 
751
785
  it "returns results sorted by weighted rank using an implied low rank for unranked columns" do
752
- loser = ModelWithPgSearch.create!(:title => 'bar', :content => 'foo')
753
- winner = ModelWithPgSearch.create!(:title => 'foo', :content => 'bar')
786
+ loser = ModelWithPgSearch.create!(title: 'bar', content: 'foo')
787
+ winner = ModelWithPgSearch.create!(title: 'foo', content: 'bar')
754
788
 
755
789
  results = ModelWithPgSearch.search_weighted('foo').with_pg_search_rank
756
790
  expect(results[0].pg_search_rank).to be > results[1].pg_search_rank
@@ -758,20 +792,20 @@ describe "an Active Record model which includes PgSearch" do
758
792
  end
759
793
  end
760
794
 
761
- context "searching any_word option" do
795
+ context "when searching any_word option" do
762
796
  before do
763
797
  ModelWithPgSearch.pg_search_scope :search_title_with_any_word,
764
- :against => :title,
765
- :using => {
766
- :tsearch => {:any_word => true}
767
- }
798
+ against: :title,
799
+ using: {
800
+ tsearch: { any_word: true }
801
+ }
768
802
 
769
803
  ModelWithPgSearch.pg_search_scope :search_title_with_all_words,
770
- :against => :title
804
+ against: :title
771
805
  end
772
806
 
773
807
  it "returns all results containing any word in their title" do
774
- numbers = %w[one two three four].map { |number| ModelWithPgSearch.create!(:title => number) }
808
+ numbers = %w[one two three four].map { |number| ModelWithPgSearch.create!(title: number) }
775
809
 
776
810
  results = ModelWithPgSearch.search_title_with_any_word("one two three four")
777
811
 
@@ -786,21 +820,21 @@ describe "an Active Record model which includes PgSearch" do
786
820
  context "with :negation" do
787
821
  before do
788
822
  ModelWithPgSearch.pg_search_scope :search_with_negation,
789
- :against => :title,
790
- :using => {
791
- :tsearch => {:negation => true}
792
- }
823
+ against: :title,
824
+ using: {
825
+ tsearch: { negation: true }
826
+ }
793
827
  end
794
828
 
795
829
  it "doesn't return results that contain terms prepended with '!'" do
796
830
  included = [
797
- ModelWithPgSearch.create!(:title => "one fish"),
798
- ModelWithPgSearch.create!(:title => "two fish")
831
+ ModelWithPgSearch.create!(title: "one fish"),
832
+ ModelWithPgSearch.create!(title: "two fish")
799
833
  ]
800
834
 
801
835
  excluded = [
802
- ModelWithPgSearch.create!(:title => "red fish"),
803
- ModelWithPgSearch.create!(:title => "blue fish")
836
+ ModelWithPgSearch.create!(title: "red fish"),
837
+ ModelWithPgSearch.create!(title: "blue fish")
804
838
  ]
805
839
 
806
840
  results = ModelWithPgSearch.search_with_negation("fish !red !blue")
@@ -813,19 +847,19 @@ describe "an Active Record model which includes PgSearch" do
813
847
  context "without :negation" do
814
848
  before do
815
849
  ModelWithPgSearch.pg_search_scope :search_without_negation,
816
- :against => :title,
817
- :using => {
818
- :tsearch => {}
819
- }
850
+ against: :title,
851
+ using: {
852
+ tsearch: {}
853
+ }
820
854
  end
821
855
 
822
856
  it "return results that contain terms prepended with '!'" do
823
857
  included = [
824
- ModelWithPgSearch.create!(:title => "!bang")
858
+ ModelWithPgSearch.create!(title: "!bang")
825
859
  ]
826
860
 
827
861
  excluded = [
828
- ModelWithPgSearch.create!(:title => "?question")
862
+ ModelWithPgSearch.create!(title: "?question")
829
863
  ]
830
864
 
831
865
  results = ModelWithPgSearch.search_without_negation("!bang")
@@ -836,30 +870,30 @@ describe "an Active Record model which includes PgSearch" do
836
870
  end
837
871
  end
838
872
 
839
- context "using dmetaphone" do
873
+ context "when using dmetaphone" do
840
874
  before do
841
875
  ModelWithPgSearch.pg_search_scope :with_dmetaphones,
842
- :against => %i[title content],
843
- :using => :dmetaphone
876
+ against: %i[title content],
877
+ using: :dmetaphone
844
878
  end
845
879
 
846
880
  it "returns rows where one searchable column and the query share enough dmetaphones" do
847
- included = ModelWithPgSearch.create!(:title => 'Geoff', :content => nil)
848
- excluded = ModelWithPgSearch.create!(:title => 'Bob', :content => nil)
881
+ included = ModelWithPgSearch.create!(title: 'Geoff', content: nil)
882
+ excluded = ModelWithPgSearch.create!(title: 'Bob', content: nil)
849
883
  results = ModelWithPgSearch.with_dmetaphones('Jeff')
850
884
  expect(results).to eq([included])
851
885
  end
852
886
 
853
887
  it "returns rows where multiple searchable columns and the query share enough dmetaphones" do
854
- included = ModelWithPgSearch.create!(:title => 'Geoff', :content => 'George')
855
- excluded = ModelWithPgSearch.create!(:title => 'Bob', :content => 'Jones')
888
+ included = ModelWithPgSearch.create!(title: 'Geoff', content: 'George')
889
+ excluded = ModelWithPgSearch.create!(title: 'Bob', content: 'Jones')
856
890
  results = ModelWithPgSearch.with_dmetaphones('Jeff Jorge')
857
891
  expect(results).to eq([included])
858
892
  end
859
893
 
860
894
  it "returns rows that match dmetaphones that are English stopwords" do
861
- included = ModelWithPgSearch.create!(:title => 'White', :content => nil)
862
- excluded = ModelWithPgSearch.create!(:title => 'Black', :content => nil)
895
+ included = ModelWithPgSearch.create!(title: 'White', content: nil)
896
+ excluded = ModelWithPgSearch.create!(title: 'Black', content: nil)
863
897
  results = ModelWithPgSearch.with_dmetaphones('Wight')
864
898
  expect(results).to eq([included])
865
899
  end
@@ -867,50 +901,50 @@ describe "an Active Record model which includes PgSearch" do
867
901
  it "can handle terms that do not have a dmetaphone equivalent" do
868
902
  term_with_blank_metaphone = "w"
869
903
 
870
- included = ModelWithPgSearch.create!(:title => 'White', :content => nil)
871
- excluded = ModelWithPgSearch.create!(:title => 'Black', :content => nil)
904
+ included = ModelWithPgSearch.create!(title: 'White', content: nil)
905
+ excluded = ModelWithPgSearch.create!(title: 'Black', content: nil)
872
906
 
873
907
  results = ModelWithPgSearch.with_dmetaphones('Wight W')
874
908
  expect(results).to eq([included])
875
909
  end
876
910
  end
877
911
 
878
- context "using multiple features" do
912
+ context "when using multiple features" do
879
913
  before do
880
914
  ModelWithPgSearch.pg_search_scope :with_tsearch,
881
- :against => :title,
882
- :using => [
883
- [:tsearch, {:dictionary => 'english'}]
884
- ]
915
+ against: :title,
916
+ using: [
917
+ [:tsearch, { dictionary: 'english' }]
918
+ ]
885
919
 
886
920
  ModelWithPgSearch.pg_search_scope :with_trigram,
887
- :against => :title,
888
- :using => :trigram
921
+ against: :title,
922
+ using: :trigram
889
923
 
890
924
  ModelWithPgSearch.pg_search_scope :with_trigram_and_ignoring_accents,
891
- :against => :title,
892
- :ignoring => :accents,
893
- :using => :trigram
925
+ against: :title,
926
+ ignoring: :accents,
927
+ using: :trigram
894
928
 
895
929
  ModelWithPgSearch.pg_search_scope :with_tsearch_and_trigram,
896
- :against => :title,
897
- :using => [
898
- [:tsearch, {:dictionary => 'english'}],
899
- :trigram
900
- ]
930
+ against: :title,
931
+ using: [
932
+ [:tsearch, { dictionary: 'english' }],
933
+ :trigram
934
+ ]
901
935
 
902
936
  ModelWithPgSearch.pg_search_scope :complex_search,
903
- :against => %i[content title],
904
- :ignoring => :accents,
905
- :using => {
906
- :tsearch => {:dictionary => 'english'},
907
- :dmetaphone => {},
908
- :trigram => {}
909
- }
937
+ against: %i[content title],
938
+ ignoring: :accents,
939
+ using: {
940
+ tsearch: { dictionary: 'english' },
941
+ dmetaphone: {},
942
+ trigram: {}
943
+ }
910
944
  end
911
945
 
912
946
  it "returns rows that match using any of the features" do
913
- record = ModelWithPgSearch.create!(:title => "tiling is grouty")
947
+ record = ModelWithPgSearch.create!(title: "tiling is grouty")
914
948
 
915
949
  # matches trigram only
916
950
  trigram_query = "ling is grouty"
@@ -948,33 +982,40 @@ describe "an Active Record model which includes PgSearch" do
948
982
  end
949
983
 
950
984
  context "with feature-specific configuration" do
951
- before do
952
- @tsearch_config = tsearch_config = {:dictionary => 'english'}
953
- @trigram_config = trigram_config = {:foo => 'bar'}
985
+ let(:tsearch_config) { { dictionary: 'english' } }
986
+ let(:trigram_config) { { foo: 'bar' } }
954
987
 
988
+ before do
955
989
  ModelWithPgSearch.pg_search_scope :with_tsearch_and_trigram_using_hash,
956
- :against => :title,
957
- :using => {
958
- :tsearch => tsearch_config,
959
- :trigram => trigram_config
960
- }
990
+ against: :title,
991
+ using: { tsearch: tsearch_config, trigram: trigram_config }
961
992
  end
962
993
 
963
- it "should pass the custom configuration down to the specified feature" do
964
- stub_feature = double(
965
- :conditions => Arel::Nodes::Grouping.new(Arel.sql("1 = 1")),
966
- :rank => Arel::Nodes::Grouping.new(Arel.sql("1.0"))
994
+ it "passes the custom configuration down to the specified feature" do
995
+ tsearch_feature = instance_double(
996
+ "PgSearch::Features::TSearch",
997
+ conditions: Arel::Nodes::Grouping.new(Arel.sql("1 = 1")),
998
+ rank: Arel::Nodes::Grouping.new(Arel.sql("1.0"))
967
999
  )
968
1000
 
969
- expect(PgSearch::Features::TSearch).to receive(:new).with(anything, @tsearch_config, anything, anything, anything).at_least(:once).and_return(stub_feature)
970
- expect(PgSearch::Features::Trigram).to receive(:new).with(anything, @trigram_config, anything, anything, anything).at_least(:once).and_return(stub_feature)
1001
+ trigram_feature = instance_double(
1002
+ "PgSearch::Features::Trigram",
1003
+ conditions: Arel::Nodes::Grouping.new(Arel.sql("1 = 1")),
1004
+ rank: Arel::Nodes::Grouping.new(Arel.sql("1.0"))
1005
+ )
1006
+
1007
+ allow(PgSearch::Features::TSearch).to receive(:new).with(anything, tsearch_config, anything, anything, anything).and_return(tsearch_feature)
1008
+ allow(PgSearch::Features::Trigram).to receive(:new).with(anything, trigram_config, anything, anything, anything).and_return(trigram_feature)
971
1009
 
972
1010
  ModelWithPgSearch.with_tsearch_and_trigram_using_hash("foo")
1011
+
1012
+ expect(PgSearch::Features::TSearch).to have_received(:new).with(anything, tsearch_config, anything, anything, anything).at_least(:once)
1013
+ expect(PgSearch::Features::Trigram).to have_received(:new).with(anything, trigram_config, anything, anything, anything).at_least(:once)
973
1014
  end
974
1015
  end
975
1016
  end
976
1017
 
977
- context "using a tsvector column and an association" do
1018
+ context "when using a tsvector column and an association" do
978
1019
  with_model :Comment do
979
1020
  table do |t|
980
1021
  t.integer :post_id
@@ -993,7 +1034,7 @@ describe "an Active Record model which includes PgSearch" do
993
1034
  end
994
1035
 
995
1036
  model do
996
- include PgSearch
1037
+ include PgSearch::Model
997
1038
  has_many :comments
998
1039
  end
999
1040
  end
@@ -1002,7 +1043,7 @@ describe "an Active Record model which includes PgSearch" do
1002
1043
  let!(:unexpected) { Post.create!(content: 'longcat is looooooooong') }
1003
1044
 
1004
1045
  before do
1005
- ActiveRecord::Base.connection.execute <<-SQL.strip_heredoc
1046
+ ActiveRecord::Base.connection.execute <<~SQL.squish
1006
1047
  UPDATE #{Post.quoted_table_name}
1007
1048
  SET content_tsvector = to_tsvector('english'::regconfig, #{Post.quoted_table_name}."content")
1008
1049
  SQL
@@ -1011,41 +1052,41 @@ describe "an Active Record model which includes PgSearch" do
1011
1052
  unexpected.comments.create(body: 'commentwo')
1012
1053
 
1013
1054
  Post.pg_search_scope :search_by_content_with_tsvector,
1014
- :associated_against => {comments: [:body]},
1015
- :using => {
1016
- :tsearch => {
1017
- :tsvector_column => 'content_tsvector',
1018
- :dictionary => 'english'
1019
- }
1020
- }
1055
+ associated_against: { comments: [:body] },
1056
+ using: {
1057
+ tsearch: {
1058
+ tsvector_column: 'content_tsvector',
1059
+ dictionary: 'english'
1060
+ }
1061
+ }
1021
1062
  end
1022
1063
 
1023
- it "should find by the tsvector column" do
1064
+ it "finds by the tsvector column" do
1024
1065
  expect(Post.search_by_content_with_tsvector("phooey").map(&:id)).to eq([expected.id])
1025
1066
  end
1026
1067
 
1027
- it "should find by the associated record" do
1068
+ it "finds by the associated record" do
1028
1069
  expect(Post.search_by_content_with_tsvector("commentone").map(&:id)).to eq([expected.id])
1029
1070
  end
1030
1071
 
1031
- it 'should find by a combination of the two' do
1072
+ it 'finds by a combination of the two' do
1032
1073
  expect(Post.search_by_content_with_tsvector("phooey commentone").map(&:id)).to eq([expected.id])
1033
1074
  end
1034
1075
  end
1035
1076
 
1036
- context 'using multiple tsvector columns' do
1077
+ context 'when using multiple tsvector columns' do
1037
1078
  with_model :ModelWithTsvector do
1038
1079
  model do
1039
- include PgSearch
1080
+ include PgSearch::Model
1040
1081
 
1041
1082
  pg_search_scope :search_by_multiple_tsvector_columns,
1042
- :against => ['content', 'message'],
1043
- :using => {
1044
- :tsearch => {
1045
- :tsvector_column => ['content_tsvector', 'message_tsvector'],
1046
- :dictionary => 'english'
1047
- }
1048
- }
1083
+ against: ['content', 'message'],
1084
+ using: {
1085
+ tsearch: {
1086
+ tsvector_column: ['content_tsvector', 'message_tsvector'],
1087
+ dictionary: 'english'
1088
+ }
1089
+ }
1049
1090
  end
1050
1091
  end
1051
1092
 
@@ -1057,40 +1098,41 @@ describe "an Active Record model which includes PgSearch" do
1057
1098
  end
1058
1099
  end
1059
1100
 
1060
- context "using a tsvector column with" do
1101
+ context "when using a tsvector column with" do
1061
1102
  with_model :ModelWithTsvector do
1062
1103
  table do |t|
1063
1104
  t.text 'content'
1064
1105
  t.tsvector 'content_tsvector'
1065
1106
  end
1066
1107
 
1067
- model { include PgSearch }
1108
+ model { include PgSearch::Model }
1068
1109
  end
1069
1110
 
1070
- let!(:expected) { ModelWithTsvector.create!(:content => 'tiling is grouty') }
1071
- let!(:unexpected) { ModelWithTsvector.create!(:content => 'longcat is looooooooong') }
1111
+ let!(:expected) { ModelWithTsvector.create!(content: 'tiling is grouty') }
1072
1112
 
1073
1113
  before do
1074
- ActiveRecord::Base.connection.execute <<-SQL.strip_heredoc
1114
+ ModelWithTsvector.create!(content: 'longcat is looooooooong')
1115
+
1116
+ ActiveRecord::Base.connection.execute <<~SQL.squish
1075
1117
  UPDATE #{ModelWithTsvector.quoted_table_name}
1076
1118
  SET content_tsvector = to_tsvector('english'::regconfig, #{ModelWithTsvector.quoted_table_name}."content")
1077
1119
  SQL
1078
1120
 
1079
1121
  ModelWithTsvector.pg_search_scope :search_by_content_with_tsvector,
1080
- :against => :content,
1081
- :using => {
1082
- :tsearch => {
1083
- :tsvector_column => 'content_tsvector',
1084
- :dictionary => 'english'
1085
- }
1086
- }
1122
+ against: :content,
1123
+ using: {
1124
+ tsearch: {
1125
+ tsvector_column: 'content_tsvector',
1126
+ dictionary: 'english'
1127
+ }
1128
+ }
1087
1129
  end
1088
1130
 
1089
- it "should not use to_tsvector in the query" do
1131
+ it "does not use to_tsvector in the query" do
1090
1132
  expect(ModelWithTsvector.search_by_content_with_tsvector("tiles").to_sql).not_to match(/to_tsvector/)
1091
1133
  end
1092
1134
 
1093
- it "should find the expected result" do
1135
+ it "finds the expected result" do
1094
1136
  expect(ModelWithTsvector.search_by_content_with_tsvector("tiles").map(&:id)).to eq([expected.id])
1095
1137
  end
1096
1138
 
@@ -1106,7 +1148,7 @@ describe "an Active Record model which includes PgSearch" do
1106
1148
  ModelWithTsvector.has_many :another_models
1107
1149
  end
1108
1150
 
1109
- it "should refer to the tsvector column in the query unambiguously" do
1151
+ it "refers to the tsvector column in the query unambiguously" do
1110
1152
  expect {
1111
1153
  ModelWithTsvector.joins(:another_models).search_by_content_with_tsvector("test").to_a
1112
1154
  }.not_to raise_exception
@@ -1114,46 +1156,55 @@ describe "an Active Record model which includes PgSearch" do
1114
1156
  end
1115
1157
  end
1116
1158
 
1117
- context "ignoring accents" do
1159
+ context "when ignoring accents" do
1118
1160
  before do
1119
1161
  ModelWithPgSearch.pg_search_scope :search_title_without_accents,
1120
- :against => :title,
1121
- :ignoring => :accents
1162
+ against: :title,
1163
+ ignoring: :accents
1122
1164
  end
1123
1165
 
1124
1166
  it "returns rows that match the query but not its accents" do
1125
1167
  # \303\241 is a with acute accent
1126
1168
  # \303\251 is e with acute accent
1127
1169
 
1128
- included = ModelWithPgSearch.create!(:title => "\303\241bcdef")
1170
+ included = ModelWithPgSearch.create!(title: "\303\241bcdef")
1129
1171
 
1130
1172
  results = ModelWithPgSearch.search_title_without_accents("abcd\303\251f")
1131
1173
  expect(results).to eq([included])
1132
1174
  end
1175
+
1176
+ context "when the query includes accents" do
1177
+ it "does not create an erroneous tsquery expression" do
1178
+ included = ModelWithPgSearch.create!(title: "Weird L‘Content")
1179
+
1180
+ results = ModelWithPgSearch.search_title_without_accents("L‘Content")
1181
+ expect(results).to eq([included])
1182
+ end
1183
+ end
1133
1184
  end
1134
1185
 
1135
1186
  context "when passed a :ranked_by expression" do
1136
1187
  before do
1137
1188
  ModelWithPgSearch.pg_search_scope :search_content_with_default_rank,
1138
- :against => :content
1189
+ against: :content
1139
1190
 
1140
1191
  ModelWithPgSearch.pg_search_scope :search_content_with_importance_as_rank,
1141
- :against => :content,
1142
- :ranked_by => "importance"
1192
+ against: :content,
1193
+ ranked_by: "importance"
1143
1194
 
1144
1195
  ModelWithPgSearch.pg_search_scope :search_content_with_importance_as_rank_multiplier,
1145
- :against => :content,
1146
- :ranked_by => ":tsearch * importance"
1196
+ against: :content,
1197
+ ranked_by: ":tsearch * importance"
1147
1198
  end
1148
1199
 
1149
- it "should return records with a rank attribute equal to the :ranked_by expression" do
1150
- ModelWithPgSearch.create!(:content => 'foo', :importance => 10)
1200
+ it "returns records with a rank attribute equal to the :ranked_by expression" do
1201
+ ModelWithPgSearch.create!(content: 'foo', importance: 10)
1151
1202
  results = ModelWithPgSearch.search_content_with_importance_as_rank("foo").with_pg_search_rank
1152
1203
  expect(results.first.pg_search_rank).to eq(10)
1153
1204
  end
1154
1205
 
1155
- it "should substitute :tsearch with the tsearch rank expression in the :ranked_by expression" do
1156
- ModelWithPgSearch.create!(:content => 'foo', :importance => 10)
1206
+ it "substitutes :tsearch with the tsearch rank expression in the :ranked_by expression" do
1207
+ ModelWithPgSearch.create!(content: 'foo', importance: 10)
1157
1208
 
1158
1209
  tsearch_result =
1159
1210
  ModelWithPgSearch.search_content_with_default_rank("foo").with_pg_search_rank.first
@@ -1162,19 +1213,19 @@ describe "an Active Record model which includes PgSearch" do
1162
1213
 
1163
1214
  multiplied_result =
1164
1215
  ModelWithPgSearch.search_content_with_importance_as_rank_multiplier("foo")
1165
- .with_pg_search_rank
1166
- .first
1216
+ .with_pg_search_rank
1217
+ .first
1167
1218
 
1168
1219
  multiplied_rank = multiplied_result.pg_search_rank
1169
1220
 
1170
1221
  expect(multiplied_rank).to be_within(0.001).of(tsearch_rank * 10)
1171
1222
  end
1172
1223
 
1173
- it "should return results in descending order of the value of the rank expression" do
1224
+ it "returns results in descending order of the value of the rank expression" do
1174
1225
  records = [
1175
- ModelWithPgSearch.create!(:content => 'foo', :importance => 1),
1176
- ModelWithPgSearch.create!(:content => 'foo', :importance => 3),
1177
- ModelWithPgSearch.create!(:content => 'foo', :importance => 2)
1226
+ ModelWithPgSearch.create!(content: 'foo', importance: 1),
1227
+ ModelWithPgSearch.create!(content: 'foo', importance: 3),
1228
+ ModelWithPgSearch.create!(content: 'foo', importance: 2)
1178
1229
  ]
1179
1230
 
1180
1231
  results = ModelWithPgSearch.search_content_with_importance_as_rank("foo")
@@ -1184,12 +1235,13 @@ describe "an Active Record model which includes PgSearch" do
1184
1235
  %w[tsearch trigram dmetaphone].each do |feature|
1185
1236
  context "using the #{feature} ranking algorithm" do
1186
1237
  let(:scope_name) { :"search_content_ranked_by_#{feature}" }
1238
+
1187
1239
  before do
1188
1240
  ModelWithPgSearch.pg_search_scope scope_name,
1189
- :against => :content,
1190
- :ranked_by => ":#{feature}"
1241
+ against: :content,
1242
+ ranked_by: ":#{feature}"
1191
1243
 
1192
- ModelWithPgSearch.create!(:content => 'foo')
1244
+ ModelWithPgSearch.create!(content: 'foo')
1193
1245
  end
1194
1246
 
1195
1247
  context "when .with_pg_search_rank is chained after" do
@@ -1220,45 +1272,45 @@ describe "an Active Record model which includes PgSearch" do
1220
1272
  end
1221
1273
  end
1222
1274
 
1223
- context "using the tsearch ranking algorithm" do
1275
+ context "when using the tsearch ranking algorithm" do
1224
1276
  it "sorts results by the tsearch rank" do
1225
1277
  ModelWithPgSearch.pg_search_scope :search_content_ranked_by_tsearch,
1226
- :using => :tsearch,
1227
- :against => :content,
1228
- :ranked_by => ":tsearch"
1278
+ using: :tsearch,
1279
+ against: :content,
1280
+ ranked_by: ":tsearch"
1229
1281
 
1230
- once = ModelWithPgSearch.create!(:content => 'foo bar')
1231
- twice = ModelWithPgSearch.create!(:content => 'foo foo')
1282
+ once = ModelWithPgSearch.create!(content: 'foo bar')
1283
+ twice = ModelWithPgSearch.create!(content: 'foo foo')
1232
1284
 
1233
1285
  results = ModelWithPgSearch.search_content_ranked_by_tsearch('foo')
1234
1286
  expect(results.find_index(twice)).to be < results.find_index(once)
1235
1287
  end
1236
1288
  end
1237
1289
 
1238
- context "using the trigram ranking algorithm" do
1290
+ context "when using the trigram ranking algorithm" do
1239
1291
  it "sorts results by the trigram rank" do
1240
1292
  ModelWithPgSearch.pg_search_scope :search_content_ranked_by_trigram,
1241
- :using => :trigram,
1242
- :against => :content,
1243
- :ranked_by => ":trigram"
1293
+ using: :trigram,
1294
+ against: :content,
1295
+ ranked_by: ":trigram"
1244
1296
 
1245
- close = ModelWithPgSearch.create!(:content => 'abcdef')
1246
- exact = ModelWithPgSearch.create!(:content => 'abc')
1297
+ close = ModelWithPgSearch.create!(content: 'abcdef')
1298
+ exact = ModelWithPgSearch.create!(content: 'abc')
1247
1299
 
1248
1300
  results = ModelWithPgSearch.search_content_ranked_by_trigram('abc')
1249
1301
  expect(results.find_index(exact)).to be < results.find_index(close)
1250
1302
  end
1251
1303
  end
1252
1304
 
1253
- context "using the dmetaphone ranking algorithm" do
1305
+ context "when using the dmetaphone ranking algorithm" do
1254
1306
  it "sorts results by the dmetaphone rank" do
1255
1307
  ModelWithPgSearch.pg_search_scope :search_content_ranked_by_dmetaphone,
1256
- :using => :dmetaphone,
1257
- :against => :content,
1258
- :ranked_by => ":dmetaphone"
1308
+ using: :dmetaphone,
1309
+ against: :content,
1310
+ ranked_by: ":dmetaphone"
1259
1311
 
1260
- once = ModelWithPgSearch.create!(:content => 'Phoo Bar')
1261
- twice = ModelWithPgSearch.create!(:content => 'Phoo Fu')
1312
+ once = ModelWithPgSearch.create!(content: 'Phoo Bar')
1313
+ twice = ModelWithPgSearch.create!(content: 'Phoo Fu')
1262
1314
 
1263
1315
  results = ModelWithPgSearch.search_content_ranked_by_dmetaphone('foo')
1264
1316
  expect(results.find_index(twice)).to be < results.find_index(once)
@@ -1269,17 +1321,17 @@ describe "an Active Record model which includes PgSearch" do
1269
1321
  context "when there is a sort only feature" do
1270
1322
  it "excludes that feature from the conditions, but uses it in the sorting" do
1271
1323
  ModelWithPgSearch.pg_search_scope :search_content_ranked_by_dmetaphone,
1272
- :against => :content,
1273
- :using => {
1274
- :tsearch => {:any_word => true, :prefix => true},
1275
- :dmetaphone => {:any_word => true, :prefix => true, :sort_only => true}
1276
- },
1277
- :ranked_by => ":tsearch + (0.5 * :dmetaphone)"
1278
-
1279
- exact = ModelWithPgSearch.create!(:content => "ash hines")
1280
- one_exact_one_close = ModelWithPgSearch.create!(:content => "ash heinz")
1281
- one_exact = ModelWithPgSearch.create!(:content => "ash smith")
1282
- one_close = ModelWithPgSearch.create!(:content => "leigh heinz")
1324
+ against: :content,
1325
+ using: {
1326
+ tsearch: { any_word: true, prefix: true },
1327
+ dmetaphone: { any_word: true, prefix: true, sort_only: true }
1328
+ },
1329
+ ranked_by: ":tsearch + (0.5 * :dmetaphone)"
1330
+
1331
+ exact = ModelWithPgSearch.create!(content: "ash hines")
1332
+ one_exact_one_close = ModelWithPgSearch.create!(content: "ash heinz")
1333
+ one_exact = ModelWithPgSearch.create!(content: "ash smith")
1334
+ one_close = ModelWithPgSearch.create!(content: "leigh heinz")
1283
1335
 
1284
1336
  results = ModelWithPgSearch.search_content_ranked_by_dmetaphone("ash hines")
1285
1337
  expect(results).to eq [exact, one_exact_one_close, one_exact]
@@ -1287,3 +1339,4 @@ describe "an Active Record model which includes PgSearch" do
1287
1339
  end
1288
1340
  end
1289
1341
  end
1342
+ # rubocop:enable RSpec/NestedGroups