pg_search 2.3.6 → 2.3.8

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