pg_search 2.1.2 → 2.2.0
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.
- checksums.yaml +5 -5
- data/.rubocop.yml +11 -7
- data/.travis.yml +33 -42
- data/CHANGELOG.md +140 -112
- data/CONTRIBUTING.md +5 -3
- data/Gemfile +6 -4
- data/LICENSE +1 -1
- data/README.md +221 -159
- data/Rakefile +3 -5
- data/lib/pg_search/configuration/association.rb +2 -0
- data/lib/pg_search/configuration/column.rb +2 -0
- data/lib/pg_search/configuration/foreign_column.rb +2 -0
- data/lib/pg_search/configuration.rb +8 -4
- data/lib/pg_search/document.rb +5 -3
- data/lib/pg_search/features/dmetaphone.rb +3 -1
- data/lib/pg_search/features/feature.rb +4 -2
- data/lib/pg_search/features/trigram.rb +31 -5
- data/lib/pg_search/features/tsearch.rb +4 -1
- data/lib/pg_search/features.rb +2 -0
- data/lib/pg_search/migration/dmetaphone_generator.rb +3 -1
- data/lib/pg_search/migration/generator.rb +4 -2
- data/lib/pg_search/migration/multisearch_generator.rb +2 -1
- data/lib/pg_search/migration/templates/create_pg_search_documents.rb.erb +1 -1
- data/lib/pg_search/multisearch/rebuilder.rb +7 -3
- data/lib/pg_search/multisearch.rb +4 -4
- data/lib/pg_search/multisearchable.rb +10 -6
- data/lib/pg_search/normalizer.rb +2 -0
- data/lib/pg_search/railtie.rb +2 -0
- data/lib/pg_search/scope_options.rb +21 -41
- data/lib/pg_search/tasks.rb +3 -0
- data/lib/pg_search/version.rb +3 -1
- data/lib/pg_search.rb +10 -5
- data/pg_search.gemspec +8 -7
- data/spec/integration/associations_spec.rb +103 -101
- data/spec/integration/pagination_spec.rb +9 -7
- data/spec/integration/pg_search_spec.rb +266 -255
- data/spec/integration/single_table_inheritance_spec.rb +16 -15
- data/spec/lib/pg_search/configuration/association_spec.rb +7 -5
- data/spec/lib/pg_search/configuration/column_spec.rb +2 -0
- data/spec/lib/pg_search/configuration/foreign_column_spec.rb +5 -3
- data/spec/lib/pg_search/features/dmetaphone_spec.rb +6 -4
- data/spec/lib/pg_search/features/trigram_spec.rb +39 -12
- data/spec/lib/pg_search/features/tsearch_spec.rb +23 -21
- data/spec/lib/pg_search/multisearch/rebuilder_spec.rb +32 -11
- data/spec/lib/pg_search/multisearch_spec.rb +9 -7
- data/spec/lib/pg_search/multisearchable_spec.rb +68 -27
- data/spec/lib/pg_search/normalizer_spec.rb +7 -5
- data/spec/lib/pg_search_spec.rb +33 -31
- data/spec/spec_helper.rb +3 -1
- data/spec/support/database.rb +16 -20
- data/spec/support/with_model.rb +2 -0
- metadata +13 -30
- data/.rubocop_todo.yml +0 -163
- data/Guardfile +0 -6
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "spec_helper"
|
2
4
|
|
3
5
|
describe "an Active Record model which includes PgSearch" do
|
@@ -28,7 +30,7 @@ describe "an Active Record model which includes PgSearch" do
|
|
28
30
|
|
29
31
|
describe ".pg_search_scope" do
|
30
32
|
it "builds a chainable scope" do
|
31
|
-
ModelWithPgSearch.pg_search_scope "matching_query", :
|
33
|
+
ModelWithPgSearch.pg_search_scope "matching_query", against: []
|
32
34
|
scope = ModelWithPgSearch.where("1 = 1").matching_query("foo").where("1 = 1")
|
33
35
|
expect(scope).to be_an ActiveRecord::Relation
|
34
36
|
end
|
@@ -36,15 +38,15 @@ describe "an Active Record model which includes PgSearch" do
|
|
36
38
|
context "when passed a lambda" do
|
37
39
|
it "builds a dynamic scope" do
|
38
40
|
ModelWithPgSearch.pg_search_scope :search_title_or_content,
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
41
|
+
lambda { |query, pick_content|
|
42
|
+
{
|
43
|
+
query: query.gsub("-remove-", ""),
|
44
|
+
against: pick_content ? :content : :title
|
45
|
+
}
|
46
|
+
}
|
45
47
|
|
46
|
-
included = ModelWithPgSearch.create!(:
|
47
|
-
excluded = ModelWithPgSearch.create!(:
|
48
|
+
included = ModelWithPgSearch.create!(title: 'foo', content: 'bar')
|
49
|
+
excluded = ModelWithPgSearch.create!(title: 'bar', content: 'foo')
|
48
50
|
|
49
51
|
expect(ModelWithPgSearch.search_title_or_content('fo-remove-o', false)).to eq([included])
|
50
52
|
expect(ModelWithPgSearch.search_title_or_content('b-remove-ar', true)).to eq([included])
|
@@ -54,8 +56,8 @@ describe "an Active Record model which includes PgSearch" do
|
|
54
56
|
context "when an unknown option is passed in" do
|
55
57
|
it "raises an exception when invoked" do
|
56
58
|
ModelWithPgSearch.pg_search_scope :with_unknown_option,
|
57
|
-
|
58
|
-
|
59
|
+
against: :content,
|
60
|
+
foo: :bar
|
59
61
|
|
60
62
|
expect {
|
61
63
|
ModelWithPgSearch.with_unknown_option("foo")
|
@@ -65,7 +67,7 @@ describe "an Active Record model which includes PgSearch" do
|
|
65
67
|
context "dynamically" do
|
66
68
|
it "raises an exception when invoked" do
|
67
69
|
ModelWithPgSearch.pg_search_scope :with_unknown_option,
|
68
|
-
|
70
|
+
->(*) { { against: :content, foo: :bar } }
|
69
71
|
|
70
72
|
expect {
|
71
73
|
ModelWithPgSearch.with_unknown_option("foo")
|
@@ -77,8 +79,8 @@ describe "an Active Record model which includes PgSearch" do
|
|
77
79
|
context "when an unknown :using is passed" do
|
78
80
|
it "raises an exception when invoked" do
|
79
81
|
ModelWithPgSearch.pg_search_scope :with_unknown_using,
|
80
|
-
|
81
|
-
|
82
|
+
against: :content,
|
83
|
+
using: :foo
|
82
84
|
|
83
85
|
expect {
|
84
86
|
ModelWithPgSearch.with_unknown_using("foo")
|
@@ -88,7 +90,7 @@ describe "an Active Record model which includes PgSearch" do
|
|
88
90
|
context "dynamically" do
|
89
91
|
it "raises an exception when invoked" do
|
90
92
|
ModelWithPgSearch.pg_search_scope :with_unknown_using,
|
91
|
-
|
93
|
+
->(*) { { against: :content, using: :foo } }
|
92
94
|
|
93
95
|
expect {
|
94
96
|
ModelWithPgSearch.with_unknown_using("foo")
|
@@ -100,8 +102,8 @@ describe "an Active Record model which includes PgSearch" do
|
|
100
102
|
context "when an unknown :ignoring is passed" do
|
101
103
|
it "raises an exception when invoked" do
|
102
104
|
ModelWithPgSearch.pg_search_scope :with_unknown_ignoring,
|
103
|
-
|
104
|
-
|
105
|
+
against: :content,
|
106
|
+
ignoring: :foo
|
105
107
|
|
106
108
|
expect {
|
107
109
|
ModelWithPgSearch.with_unknown_ignoring("foo")
|
@@ -111,7 +113,7 @@ describe "an Active Record model which includes PgSearch" do
|
|
111
113
|
context "dynamically" do
|
112
114
|
it "raises an exception when invoked" do
|
113
115
|
ModelWithPgSearch.pg_search_scope :with_unknown_ignoring,
|
114
|
-
|
116
|
+
->(*) { { against: :content, ignoring: :foo } }
|
115
117
|
|
116
118
|
expect {
|
117
119
|
ModelWithPgSearch.with_unknown_ignoring("foo")
|
@@ -130,7 +132,7 @@ describe "an Active Record model which includes PgSearch" do
|
|
130
132
|
|
131
133
|
context "dynamically" do
|
132
134
|
it "raises an exception when invoked" do
|
133
|
-
ModelWithPgSearch.pg_search_scope :with_unknown_ignoring, ->(*){ {} }
|
135
|
+
ModelWithPgSearch.pg_search_scope :with_unknown_ignoring, ->(*) { {} }
|
134
136
|
|
135
137
|
expect {
|
136
138
|
ModelWithPgSearch.with_unknown_ignoring("foo")
|
@@ -144,7 +146,7 @@ describe "an Active Record model which includes PgSearch" do
|
|
144
146
|
describe "a search scope" do
|
145
147
|
context "against a single column" do
|
146
148
|
before do
|
147
|
-
ModelWithPgSearch.pg_search_scope :search_content, :
|
149
|
+
ModelWithPgSearch.pg_search_scope :search_content, against: :content
|
148
150
|
end
|
149
151
|
|
150
152
|
context "when chained after a select() scope" do
|
@@ -222,7 +224,7 @@ describe "an Active Record model which includes PgSearch" do
|
|
222
224
|
has_many :houses
|
223
225
|
pg_search_scope :named, against: [:name]
|
224
226
|
scope :with_house_in_city, lambda { |city|
|
225
|
-
joins(:houses).where(House.table_name.to_sym => {city: city})
|
227
|
+
joins(:houses).where(House.table_name.to_sym => { city: city })
|
226
228
|
}
|
227
229
|
scope :house_search_city, lambda { |query|
|
228
230
|
joins(:houses).merge(House.search_city(query))
|
@@ -286,15 +288,15 @@ describe "an Active Record model which includes PgSearch" do
|
|
286
288
|
end
|
287
289
|
|
288
290
|
it "returns an empty array when a blank query is passed in" do
|
289
|
-
ModelWithPgSearch.create!(:
|
291
|
+
ModelWithPgSearch.create!(content: 'foo')
|
290
292
|
|
291
293
|
results = ModelWithPgSearch.search_content('')
|
292
294
|
expect(results).to eq([])
|
293
295
|
end
|
294
296
|
|
295
297
|
it "returns rows where the column contains the term in the query" do
|
296
|
-
included = ModelWithPgSearch.create!(:
|
297
|
-
excluded = ModelWithPgSearch.create!(:
|
298
|
+
included = ModelWithPgSearch.create!(content: 'foo')
|
299
|
+
excluded = ModelWithPgSearch.create!(content: 'bar')
|
298
300
|
|
299
301
|
results = ModelWithPgSearch.search_content('foo')
|
300
302
|
expect(results).to include(included)
|
@@ -302,24 +304,24 @@ describe "an Active Record model which includes PgSearch" do
|
|
302
304
|
end
|
303
305
|
|
304
306
|
it "returns the correct count" do
|
305
|
-
ModelWithPgSearch.create!(:
|
306
|
-
ModelWithPgSearch.create!(:
|
307
|
+
ModelWithPgSearch.create!(content: 'foo')
|
308
|
+
ModelWithPgSearch.create!(content: 'bar')
|
307
309
|
|
308
310
|
results = ModelWithPgSearch.search_content('foo')
|
309
311
|
expect(results.count).to eq 1
|
310
312
|
end
|
311
313
|
|
312
314
|
it "returns the correct count(:all)" do
|
313
|
-
ModelWithPgSearch.create!(:
|
314
|
-
ModelWithPgSearch.create!(:
|
315
|
+
ModelWithPgSearch.create!(content: 'foo')
|
316
|
+
ModelWithPgSearch.create!(content: 'bar')
|
315
317
|
|
316
318
|
results = ModelWithPgSearch.search_content('foo')
|
317
319
|
expect(results.count(:all)).to eq 1
|
318
320
|
end
|
319
321
|
|
320
322
|
it "supports #select" do
|
321
|
-
record = ModelWithPgSearch.create!(:
|
322
|
-
other_record = ModelWithPgSearch.create!(:
|
323
|
+
record = ModelWithPgSearch.create!(content: 'foo')
|
324
|
+
other_record = ModelWithPgSearch.create!(content: 'bar')
|
323
325
|
|
324
326
|
records_with_only_id = ModelWithPgSearch.search_content('foo').select('id')
|
325
327
|
expect(records_with_only_id.length).to eq 1
|
@@ -330,27 +332,27 @@ describe "an Active Record model which includes PgSearch" do
|
|
330
332
|
end
|
331
333
|
|
332
334
|
it "supports #pluck" do
|
333
|
-
record = ModelWithPgSearch.create!(:
|
334
|
-
other_record = ModelWithPgSearch.create!(:
|
335
|
+
record = ModelWithPgSearch.create!(content: 'foo')
|
336
|
+
other_record = ModelWithPgSearch.create!(content: 'bar')
|
335
337
|
|
336
338
|
ids = ModelWithPgSearch.search_content('foo').pluck('id')
|
337
339
|
expect(ids).to eq [record.id]
|
338
340
|
end
|
339
341
|
|
340
342
|
it "supports adding where clauses using the pg_search.rank" do
|
341
|
-
once = ModelWithPgSearch.create!(:
|
342
|
-
twice = ModelWithPgSearch.create!(:
|
343
|
+
once = ModelWithPgSearch.create!(content: 'foo bar')
|
344
|
+
twice = ModelWithPgSearch.create!(content: 'foo foo')
|
343
345
|
|
344
346
|
records = ModelWithPgSearch.search_content('foo')
|
345
|
-
|
347
|
+
.where("#{PgSearch::Configuration.alias(ModelWithPgSearch.table_name)}.rank > 0.07")
|
346
348
|
|
347
349
|
expect(records).to eq [twice]
|
348
350
|
end
|
349
351
|
|
350
352
|
it "returns rows where the column contains all the terms in the query in any order" do
|
351
|
-
included = [ModelWithPgSearch.create!(:
|
352
|
-
ModelWithPgSearch.create!(:
|
353
|
-
excluded = ModelWithPgSearch.create!(:
|
353
|
+
included = [ModelWithPgSearch.create!(content: 'foo bar'),
|
354
|
+
ModelWithPgSearch.create!(content: 'bar foo')]
|
355
|
+
excluded = ModelWithPgSearch.create!(content: 'foo')
|
354
356
|
|
355
357
|
results = ModelWithPgSearch.search_content('foo bar')
|
356
358
|
expect(results).to match_array(included)
|
@@ -358,8 +360,8 @@ describe "an Active Record model which includes PgSearch" do
|
|
358
360
|
end
|
359
361
|
|
360
362
|
it "returns rows that match the query but not its case" do
|
361
|
-
included = [ModelWithPgSearch.create!(:
|
362
|
-
ModelWithPgSearch.create!(:
|
363
|
+
included = [ModelWithPgSearch.create!(content: "foo"),
|
364
|
+
ModelWithPgSearch.create!(content: "FOO")]
|
363
365
|
|
364
366
|
results = ModelWithPgSearch.search_content("Foo")
|
365
367
|
expect(results).to match_array(included)
|
@@ -369,8 +371,8 @@ describe "an Active Record model which includes PgSearch" do
|
|
369
371
|
# \303\241 is a with acute accent
|
370
372
|
# \303\251 is e with acute accent
|
371
373
|
|
372
|
-
included = ModelWithPgSearch.create!(:
|
373
|
-
excluded = ModelWithPgSearch.create!(:
|
374
|
+
included = ModelWithPgSearch.create!(content: "abcd\303\251f")
|
375
|
+
excluded = ModelWithPgSearch.create!(content: "\303\241bcdef")
|
374
376
|
|
375
377
|
results = ModelWithPgSearch.search_content("abcd\303\251f")
|
376
378
|
expect(results).to eq([included])
|
@@ -378,8 +380,8 @@ describe "an Active Record model which includes PgSearch" do
|
|
378
380
|
end
|
379
381
|
|
380
382
|
it "returns rows that match the query but not rows that are prefixed by the query" do
|
381
|
-
included = ModelWithPgSearch.create!(:
|
382
|
-
excluded = ModelWithPgSearch.create!(:
|
383
|
+
included = ModelWithPgSearch.create!(content: 'pre')
|
384
|
+
excluded = ModelWithPgSearch.create!(content: 'prefix')
|
383
385
|
|
384
386
|
results = ModelWithPgSearch.search_content("pre")
|
385
387
|
expect(results).to eq([included])
|
@@ -387,17 +389,17 @@ describe "an Active Record model which includes PgSearch" do
|
|
387
389
|
end
|
388
390
|
|
389
391
|
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!(:
|
391
|
-
excluded = [ModelWithPgSearch.create!(:
|
392
|
-
ModelWithPgSearch.create!(:
|
392
|
+
included = ModelWithPgSearch.create!(content: "jumped")
|
393
|
+
excluded = [ModelWithPgSearch.create!(content: "jump"),
|
394
|
+
ModelWithPgSearch.create!(content: "jumping")]
|
393
395
|
|
394
396
|
results = ModelWithPgSearch.search_content("jumped")
|
395
397
|
expect(results).to eq([included])
|
396
398
|
end
|
397
399
|
|
398
400
|
it "returns rows that match sorted by rank" do
|
399
|
-
loser = ModelWithPgSearch.create!(:
|
400
|
-
winner = ModelWithPgSearch.create!(:
|
401
|
+
loser = ModelWithPgSearch.create!(content: 'foo')
|
402
|
+
winner = ModelWithPgSearch.create!(content: 'foo foo')
|
401
403
|
|
402
404
|
results = ModelWithPgSearch.search_content("foo").with_pg_search_rank
|
403
405
|
expect(results[0].pg_search_rank).to be > results[1].pg_search_rank
|
@@ -407,8 +409,8 @@ describe "an Active Record model which includes PgSearch" do
|
|
407
409
|
it 'allows pg_search_rank along with a join' do
|
408
410
|
parent_1 = ParentModel.create!(id: 98)
|
409
411
|
parent_2 = ParentModel.create!(id: 99)
|
410
|
-
loser = ModelWithPgSearch.create!(:
|
411
|
-
winner = ModelWithPgSearch.create!(:
|
412
|
+
loser = ModelWithPgSearch.create!(content: 'foo', parent_model: parent_2)
|
413
|
+
winner = ModelWithPgSearch.create!(content: 'foo foo', parent_model: parent_1)
|
412
414
|
|
413
415
|
results = ModelWithPgSearch.joins(:parent_model).merge(ParentModel.active).search_content("foo").with_pg_search_rank
|
414
416
|
expect(results.map(&:id)).to eq [winner.id, loser.id]
|
@@ -417,8 +419,8 @@ describe "an Active Record model which includes PgSearch" do
|
|
417
419
|
end
|
418
420
|
|
419
421
|
it "returns results that match sorted by primary key for records that rank the same" do
|
420
|
-
sorted_results = [ModelWithPgSearch.create!(:
|
421
|
-
ModelWithPgSearch.create!(:
|
422
|
+
sorted_results = [ModelWithPgSearch.create!(content: 'foo'),
|
423
|
+
ModelWithPgSearch.create!(content: 'foo')].sort_by(&:id)
|
422
424
|
|
423
425
|
results = ModelWithPgSearch.search_content("foo")
|
424
426
|
expect(results).to eq(sorted_results)
|
@@ -426,13 +428,13 @@ describe "an Active Record model which includes PgSearch" do
|
|
426
428
|
|
427
429
|
it "returns results that match a query with multiple space-separated search terms" do
|
428
430
|
included = [
|
429
|
-
ModelWithPgSearch.create!(:
|
430
|
-
ModelWithPgSearch.create!(:
|
431
|
-
ModelWithPgSearch.create!(:
|
431
|
+
ModelWithPgSearch.create!(content: 'foo bar'),
|
432
|
+
ModelWithPgSearch.create!(content: 'bar foo'),
|
433
|
+
ModelWithPgSearch.create!(content: 'bar foo baz')
|
432
434
|
]
|
433
435
|
excluded = [
|
434
|
-
ModelWithPgSearch.create!(:
|
435
|
-
ModelWithPgSearch.create!(:
|
436
|
+
ModelWithPgSearch.create!(content: 'foo'),
|
437
|
+
ModelWithPgSearch.create!(content: 'foo baz')
|
436
438
|
]
|
437
439
|
|
438
440
|
results = ModelWithPgSearch.search_content('foo bar')
|
@@ -441,15 +443,15 @@ describe "an Active Record model which includes PgSearch" do
|
|
441
443
|
end
|
442
444
|
|
443
445
|
it "returns rows that match a query with characters that are invalid in a tsquery expression" do
|
444
|
-
included = ModelWithPgSearch.create!(:
|
446
|
+
included = ModelWithPgSearch.create!(content: "(:Foo.) Bar?, \\")
|
445
447
|
|
446
448
|
results = ModelWithPgSearch.search_content("foo :bar .,?() \\")
|
447
449
|
expect(results).to eq([included])
|
448
450
|
end
|
449
451
|
|
450
452
|
it "accepts non-string queries and calls #to_s on them" do
|
451
|
-
foo = ModelWithPgSearch.create!(:
|
452
|
-
not_a_string = double(:
|
453
|
+
foo = ModelWithPgSearch.create!(content: "foo")
|
454
|
+
not_a_string = double(to_s: "foo")
|
453
455
|
expect(ModelWithPgSearch.search_content(not_a_string)).to eq([foo])
|
454
456
|
end
|
455
457
|
|
@@ -465,7 +467,7 @@ describe "an Active Record model which includes PgSearch" do
|
|
465
467
|
# WARNING: searching timestamps is not something PostgreSQL
|
466
468
|
# full-text search is good at. Use at your own risk.
|
467
469
|
pg_search_scope :search_timestamps,
|
468
|
-
|
470
|
+
against: %i[created_at updated_at]
|
469
471
|
end
|
470
472
|
end
|
471
473
|
|
@@ -481,17 +483,17 @@ describe "an Active Record model which includes PgSearch" do
|
|
481
483
|
|
482
484
|
context "against multiple columns" do
|
483
485
|
before do
|
484
|
-
ModelWithPgSearch.pg_search_scope :search_title_and_content, :
|
486
|
+
ModelWithPgSearch.pg_search_scope :search_title_and_content, against: %i[title content]
|
485
487
|
end
|
486
488
|
|
487
489
|
it "returns rows whose columns contain all of the terms in the query across columns" do
|
488
490
|
included = [
|
489
|
-
ModelWithPgSearch.create!(:
|
490
|
-
ModelWithPgSearch.create!(:
|
491
|
+
ModelWithPgSearch.create!(title: 'foo', content: 'bar'),
|
492
|
+
ModelWithPgSearch.create!(title: 'bar', content: 'foo')
|
491
493
|
]
|
492
494
|
excluded = [
|
493
|
-
ModelWithPgSearch.create!(:
|
494
|
-
ModelWithPgSearch.create!(:
|
495
|
+
ModelWithPgSearch.create!(title: 'foo', content: 'foo'),
|
496
|
+
ModelWithPgSearch.create!(title: 'bar', content: 'bar')
|
495
497
|
]
|
496
498
|
|
497
499
|
results = ModelWithPgSearch.search_title_and_content('foo bar')
|
@@ -503,8 +505,8 @@ describe "an Active Record model which includes PgSearch" do
|
|
503
505
|
end
|
504
506
|
|
505
507
|
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!(:
|
507
|
-
in_content = ModelWithPgSearch.create!(:
|
508
|
+
in_title = ModelWithPgSearch.create!(title: 'foo', content: 'bar')
|
509
|
+
in_content = ModelWithPgSearch.create!(title: 'bar', content: 'foo')
|
508
510
|
|
509
511
|
results = ModelWithPgSearch.search_title_and_content('foo')
|
510
512
|
expect(results).to match_array([in_title, in_content])
|
@@ -512,7 +514,7 @@ describe "an Active Record model which includes PgSearch" do
|
|
512
514
|
|
513
515
|
# Searching with a NULL column will prevent any matches unless we coalesce it.
|
514
516
|
it "returns rows where at one column contains all of the terms in the query and another is NULL" do
|
515
|
-
included = ModelWithPgSearch.create!(:
|
517
|
+
included = ModelWithPgSearch.create!(title: 'foo', content: nil)
|
516
518
|
results = ModelWithPgSearch.search_title_and_content('foo')
|
517
519
|
expect(results).to eq([included])
|
518
520
|
end
|
@@ -520,31 +522,31 @@ describe "an Active Record model which includes PgSearch" do
|
|
520
522
|
|
521
523
|
context "using trigram" do
|
522
524
|
before do
|
523
|
-
ModelWithPgSearch.pg_search_scope :with_trigrams, :
|
525
|
+
ModelWithPgSearch.pg_search_scope :with_trigrams, against: %i[title content], using: :trigram
|
524
526
|
end
|
525
527
|
|
526
528
|
it "returns rows where one searchable column and the query share enough trigrams" do
|
527
|
-
included = ModelWithPgSearch.create!(:
|
529
|
+
included = ModelWithPgSearch.create!(title: 'abcdefghijkl', content: nil)
|
528
530
|
results = ModelWithPgSearch.with_trigrams('cdefhijkl')
|
529
531
|
expect(results).to eq([included])
|
530
532
|
end
|
531
533
|
|
532
534
|
it "returns rows where multiple searchable columns and the query share enough trigrams" do
|
533
|
-
included = ModelWithPgSearch.create!(:
|
535
|
+
included = ModelWithPgSearch.create!(title: 'abcdef', content: 'ghijkl')
|
534
536
|
results = ModelWithPgSearch.with_trigrams('cdefhijkl')
|
535
537
|
expect(results).to eq([included])
|
536
538
|
end
|
537
539
|
|
538
540
|
context "when a threshold is specified" do
|
539
541
|
before do
|
540
|
-
ModelWithPgSearch.pg_search_scope :with_strict_trigrams, :
|
541
|
-
ModelWithPgSearch.pg_search_scope :with_permissive_trigrams, :
|
542
|
+
ModelWithPgSearch.pg_search_scope :with_strict_trigrams, against: %i[title content], using: { trigram: { threshold: 0.5 } }
|
543
|
+
ModelWithPgSearch.pg_search_scope :with_permissive_trigrams, against: %i[title content], using: { trigram: { threshold: 0.1 } }
|
542
544
|
end
|
543
545
|
|
544
546
|
it "uses the threshold in the trigram expression" do
|
545
|
-
low_similarity = ModelWithPgSearch.create!(:
|
546
|
-
medium_similarity = ModelWithPgSearch.create!(:
|
547
|
-
high_similarity = ModelWithPgSearch.create!(:
|
547
|
+
low_similarity = ModelWithPgSearch.create!(title: "a")
|
548
|
+
medium_similarity = ModelWithPgSearch.create!(title: "abc")
|
549
|
+
high_similarity = ModelWithPgSearch.create!(title: "abcdefghijkl")
|
548
550
|
|
549
551
|
results = ModelWithPgSearch.with_strict_trigrams("abcdefg")
|
550
552
|
expect(results).to include(high_similarity)
|
@@ -563,16 +565,16 @@ describe "an Active Record model which includes PgSearch" do
|
|
563
565
|
context "using tsearch" do
|
564
566
|
before do
|
565
567
|
ModelWithPgSearch.pg_search_scope :search_title_with_prefixes,
|
566
|
-
|
567
|
-
|
568
|
-
|
569
|
-
|
568
|
+
against: :title,
|
569
|
+
using: {
|
570
|
+
tsearch: { prefix: true }
|
571
|
+
}
|
570
572
|
end
|
571
573
|
|
572
|
-
context "with :
|
574
|
+
context "with prefix: true" do
|
573
575
|
it "returns rows that match the query and that are prefixed by the query" do
|
574
|
-
included = ModelWithPgSearch.create!(:
|
575
|
-
excluded = ModelWithPgSearch.create!(:
|
576
|
+
included = ModelWithPgSearch.create!(title: 'prefix')
|
577
|
+
excluded = ModelWithPgSearch.create!(title: 'postfix')
|
576
578
|
|
577
579
|
results = ModelWithPgSearch.search_title_with_prefixes("pre")
|
578
580
|
expect(results).to eq([included])
|
@@ -580,8 +582,8 @@ describe "an Active Record model which includes PgSearch" do
|
|
580
582
|
end
|
581
583
|
|
582
584
|
it "returns rows that match the query when the query has a hyphen" do
|
583
|
-
included = ModelWithPgSearch.create!(:
|
584
|
-
excluded = ModelWithPgSearch.create!(:
|
585
|
+
included = ModelWithPgSearch.create!(title: 'foo-bar')
|
586
|
+
excluded = ModelWithPgSearch.create!(title: 'foo bar')
|
585
587
|
|
586
588
|
results = ModelWithPgSearch.search_title_with_prefixes("foo-bar")
|
587
589
|
expect(results).to include(included)
|
@@ -592,16 +594,16 @@ describe "an Active Record model which includes PgSearch" do
|
|
592
594
|
context "with the english dictionary" do
|
593
595
|
before do
|
594
596
|
ModelWithPgSearch.pg_search_scope :search_content_with_english,
|
595
|
-
|
596
|
-
|
597
|
-
|
598
|
-
|
597
|
+
against: :content,
|
598
|
+
using: {
|
599
|
+
tsearch: { dictionary: :english }
|
600
|
+
}
|
599
601
|
end
|
600
602
|
|
601
603
|
it "returns rows that match the query when stemmed by the english dictionary" do
|
602
|
-
included = [ModelWithPgSearch.create!(:
|
603
|
-
ModelWithPgSearch.create!(:
|
604
|
-
ModelWithPgSearch.create!(:
|
604
|
+
included = [ModelWithPgSearch.create!(content: "jump"),
|
605
|
+
ModelWithPgSearch.create!(content: "jumped"),
|
606
|
+
ModelWithPgSearch.create!(content: "jumping")]
|
605
607
|
|
606
608
|
results = ModelWithPgSearch.search_content_with_english("jump")
|
607
609
|
expect(results).to match_array(included)
|
@@ -611,14 +613,14 @@ describe "an Active Record model which includes PgSearch" do
|
|
611
613
|
describe "highlighting" do
|
612
614
|
before do
|
613
615
|
["Strip Down", "Down", "Down and Out", "Won't Let You Down"].each do |name|
|
614
|
-
ModelWithPgSearch.create! :
|
616
|
+
ModelWithPgSearch.create! content: name
|
615
617
|
end
|
616
618
|
end
|
617
619
|
|
618
620
|
context "with highlight turned on" do
|
619
621
|
before do
|
620
622
|
ModelWithPgSearch.pg_search_scope :search_content,
|
621
|
-
|
623
|
+
against: :content
|
622
624
|
end
|
623
625
|
|
624
626
|
it "adds a #pg_search_highlight method to each returned model record" do
|
@@ -636,22 +638,22 @@ describe "an Active Record model which includes PgSearch" do
|
|
636
638
|
|
637
639
|
context "with custom highlighting options" do
|
638
640
|
before do
|
639
|
-
ModelWithPgSearch.create! :
|
641
|
+
ModelWithPgSearch.create! content: "#{'text ' * 2}Let #{'text ' * 2}Let #{'text ' * 2}"
|
640
642
|
|
641
643
|
ModelWithPgSearch.pg_search_scope :search_content,
|
642
|
-
|
643
|
-
|
644
|
-
|
645
|
-
|
646
|
-
|
647
|
-
|
648
|
-
|
649
|
-
|
650
|
-
|
651
|
-
|
652
|
-
|
653
|
-
|
654
|
-
|
644
|
+
against: :content,
|
645
|
+
using: {
|
646
|
+
tsearch: {
|
647
|
+
highlight: {
|
648
|
+
StartSel: '<mark class="highlight">',
|
649
|
+
StopSel: '</mark>',
|
650
|
+
FragmentDelimiter: '<delim class="my_delim">',
|
651
|
+
MaxFragments: 2,
|
652
|
+
MaxWords: 2,
|
653
|
+
MinWords: 1
|
654
|
+
}
|
655
|
+
}
|
656
|
+
}
|
655
657
|
end
|
656
658
|
|
657
659
|
it "applies the options to the excerpts" do
|
@@ -665,12 +667,12 @@ describe "an Active Record model which includes PgSearch" do
|
|
665
667
|
describe "ranking" do
|
666
668
|
before do
|
667
669
|
["Strip Down", "Down", "Down and Out", "Won't Let You Down"].each do |name|
|
668
|
-
ModelWithPgSearch.create! :
|
670
|
+
ModelWithPgSearch.create! content: name
|
669
671
|
end
|
670
672
|
end
|
671
673
|
|
672
674
|
it "adds a #pg_search_rank method to each returned model record" do
|
673
|
-
ModelWithPgSearch.pg_search_scope :search_content, :
|
675
|
+
ModelWithPgSearch.pg_search_scope :search_content, against: :content
|
674
676
|
|
675
677
|
result = ModelWithPgSearch.search_content("Strip Down").with_pg_search_rank.first
|
676
678
|
|
@@ -680,10 +682,10 @@ describe "an Active Record model which includes PgSearch" do
|
|
680
682
|
context "with a normalization specified" do
|
681
683
|
before do
|
682
684
|
ModelWithPgSearch.pg_search_scope :search_content_with_normalization,
|
683
|
-
|
684
|
-
|
685
|
-
|
686
|
-
|
685
|
+
against: :content,
|
686
|
+
using: {
|
687
|
+
tsearch: { normalization: 2 }
|
688
|
+
}
|
687
689
|
end
|
688
690
|
|
689
691
|
it "ranks the results for documents with less text higher" do
|
@@ -697,8 +699,8 @@ describe "an Active Record model which includes PgSearch" do
|
|
697
699
|
context "with no normalization" do
|
698
700
|
before do
|
699
701
|
ModelWithPgSearch.pg_search_scope :search_content_without_normalization,
|
700
|
-
|
701
|
-
|
702
|
+
against: :content,
|
703
|
+
using: :tsearch
|
702
704
|
end
|
703
705
|
|
704
706
|
it "ranks the results equally" do
|
@@ -713,12 +715,12 @@ describe "an Active Record model which includes PgSearch" do
|
|
713
715
|
context "against columns ranked with arrays" do
|
714
716
|
before do
|
715
717
|
ModelWithPgSearch.pg_search_scope :search_weighted_by_array_of_arrays,
|
716
|
-
|
718
|
+
against: [[:content, 'B'], [:title, 'A']]
|
717
719
|
end
|
718
720
|
|
719
721
|
it "returns results sorted by weighted rank" do
|
720
|
-
loser = ModelWithPgSearch.create!(:
|
721
|
-
winner = ModelWithPgSearch.create!(:
|
722
|
+
loser = ModelWithPgSearch.create!(title: 'bar', content: 'foo')
|
723
|
+
winner = ModelWithPgSearch.create!(title: 'foo', content: 'bar')
|
722
724
|
|
723
725
|
results = ModelWithPgSearch.search_weighted_by_array_of_arrays('foo').with_pg_search_rank
|
724
726
|
expect(results[0].pg_search_rank).to be > results[1].pg_search_rank
|
@@ -729,12 +731,12 @@ describe "an Active Record model which includes PgSearch" do
|
|
729
731
|
context "against columns ranked with a hash" do
|
730
732
|
before do
|
731
733
|
ModelWithPgSearch.pg_search_scope :search_weighted_by_hash,
|
732
|
-
|
734
|
+
against: { content: 'B', title: 'A' }
|
733
735
|
end
|
734
736
|
|
735
737
|
it "returns results sorted by weighted rank" do
|
736
|
-
loser = ModelWithPgSearch.create!(:
|
737
|
-
winner = ModelWithPgSearch.create!(:
|
738
|
+
loser = ModelWithPgSearch.create!(title: 'bar', content: 'foo')
|
739
|
+
winner = ModelWithPgSearch.create!(title: 'foo', content: 'bar')
|
738
740
|
|
739
741
|
results = ModelWithPgSearch.search_weighted_by_hash('foo').with_pg_search_rank
|
740
742
|
expect(results[0].pg_search_rank).to be > results[1].pg_search_rank
|
@@ -745,12 +747,12 @@ describe "an Active Record model which includes PgSearch" do
|
|
745
747
|
context "against columns of which only some are ranked" do
|
746
748
|
before do
|
747
749
|
ModelWithPgSearch.pg_search_scope :search_weighted,
|
748
|
-
|
750
|
+
against: [:content, [:title, 'A']]
|
749
751
|
end
|
750
752
|
|
751
753
|
it "returns results sorted by weighted rank using an implied low rank for unranked columns" do
|
752
|
-
loser = ModelWithPgSearch.create!(:
|
753
|
-
winner = ModelWithPgSearch.create!(:
|
754
|
+
loser = ModelWithPgSearch.create!(title: 'bar', content: 'foo')
|
755
|
+
winner = ModelWithPgSearch.create!(title: 'foo', content: 'bar')
|
754
756
|
|
755
757
|
results = ModelWithPgSearch.search_weighted('foo').with_pg_search_rank
|
756
758
|
expect(results[0].pg_search_rank).to be > results[1].pg_search_rank
|
@@ -761,17 +763,17 @@ describe "an Active Record model which includes PgSearch" do
|
|
761
763
|
context "searching any_word option" do
|
762
764
|
before do
|
763
765
|
ModelWithPgSearch.pg_search_scope :search_title_with_any_word,
|
764
|
-
|
765
|
-
|
766
|
-
|
767
|
-
|
766
|
+
against: :title,
|
767
|
+
using: {
|
768
|
+
tsearch: { any_word: true }
|
769
|
+
}
|
768
770
|
|
769
771
|
ModelWithPgSearch.pg_search_scope :search_title_with_all_words,
|
770
|
-
|
772
|
+
against: :title
|
771
773
|
end
|
772
774
|
|
773
775
|
it "returns all results containing any word in their title" do
|
774
|
-
numbers = %w[one two three four].map { |number| ModelWithPgSearch.create!(:
|
776
|
+
numbers = %w[one two three four].map { |number| ModelWithPgSearch.create!(title: number) }
|
775
777
|
|
776
778
|
results = ModelWithPgSearch.search_title_with_any_word("one two three four")
|
777
779
|
|
@@ -786,21 +788,21 @@ describe "an Active Record model which includes PgSearch" do
|
|
786
788
|
context "with :negation" do
|
787
789
|
before do
|
788
790
|
ModelWithPgSearch.pg_search_scope :search_with_negation,
|
789
|
-
|
790
|
-
|
791
|
-
|
792
|
-
|
791
|
+
against: :title,
|
792
|
+
using: {
|
793
|
+
tsearch: { negation: true }
|
794
|
+
}
|
793
795
|
end
|
794
796
|
|
795
797
|
it "doesn't return results that contain terms prepended with '!'" do
|
796
798
|
included = [
|
797
|
-
ModelWithPgSearch.create!(:
|
798
|
-
ModelWithPgSearch.create!(:
|
799
|
+
ModelWithPgSearch.create!(title: "one fish"),
|
800
|
+
ModelWithPgSearch.create!(title: "two fish")
|
799
801
|
]
|
800
802
|
|
801
803
|
excluded = [
|
802
|
-
ModelWithPgSearch.create!(:
|
803
|
-
ModelWithPgSearch.create!(:
|
804
|
+
ModelWithPgSearch.create!(title: "red fish"),
|
805
|
+
ModelWithPgSearch.create!(title: "blue fish")
|
804
806
|
]
|
805
807
|
|
806
808
|
results = ModelWithPgSearch.search_with_negation("fish !red !blue")
|
@@ -813,19 +815,19 @@ describe "an Active Record model which includes PgSearch" do
|
|
813
815
|
context "without :negation" do
|
814
816
|
before do
|
815
817
|
ModelWithPgSearch.pg_search_scope :search_without_negation,
|
816
|
-
|
817
|
-
|
818
|
-
|
819
|
-
|
818
|
+
against: :title,
|
819
|
+
using: {
|
820
|
+
tsearch: {}
|
821
|
+
}
|
820
822
|
end
|
821
823
|
|
822
824
|
it "return results that contain terms prepended with '!'" do
|
823
825
|
included = [
|
824
|
-
ModelWithPgSearch.create!(:
|
826
|
+
ModelWithPgSearch.create!(title: "!bang")
|
825
827
|
]
|
826
828
|
|
827
829
|
excluded = [
|
828
|
-
ModelWithPgSearch.create!(:
|
830
|
+
ModelWithPgSearch.create!(title: "?question")
|
829
831
|
]
|
830
832
|
|
831
833
|
results = ModelWithPgSearch.search_without_negation("!bang")
|
@@ -839,27 +841,27 @@ describe "an Active Record model which includes PgSearch" do
|
|
839
841
|
context "using dmetaphone" do
|
840
842
|
before do
|
841
843
|
ModelWithPgSearch.pg_search_scope :with_dmetaphones,
|
842
|
-
|
843
|
-
|
844
|
+
against: %i[title content],
|
845
|
+
using: :dmetaphone
|
844
846
|
end
|
845
847
|
|
846
848
|
it "returns rows where one searchable column and the query share enough dmetaphones" do
|
847
|
-
included = ModelWithPgSearch.create!(:
|
848
|
-
excluded = ModelWithPgSearch.create!(:
|
849
|
+
included = ModelWithPgSearch.create!(title: 'Geoff', content: nil)
|
850
|
+
excluded = ModelWithPgSearch.create!(title: 'Bob', content: nil)
|
849
851
|
results = ModelWithPgSearch.with_dmetaphones('Jeff')
|
850
852
|
expect(results).to eq([included])
|
851
853
|
end
|
852
854
|
|
853
855
|
it "returns rows where multiple searchable columns and the query share enough dmetaphones" do
|
854
|
-
included = ModelWithPgSearch.create!(:
|
855
|
-
excluded = ModelWithPgSearch.create!(:
|
856
|
+
included = ModelWithPgSearch.create!(title: 'Geoff', content: 'George')
|
857
|
+
excluded = ModelWithPgSearch.create!(title: 'Bob', content: 'Jones')
|
856
858
|
results = ModelWithPgSearch.with_dmetaphones('Jeff Jorge')
|
857
859
|
expect(results).to eq([included])
|
858
860
|
end
|
859
861
|
|
860
862
|
it "returns rows that match dmetaphones that are English stopwords" do
|
861
|
-
included = ModelWithPgSearch.create!(:
|
862
|
-
excluded = ModelWithPgSearch.create!(:
|
863
|
+
included = ModelWithPgSearch.create!(title: 'White', content: nil)
|
864
|
+
excluded = ModelWithPgSearch.create!(title: 'Black', content: nil)
|
863
865
|
results = ModelWithPgSearch.with_dmetaphones('Wight')
|
864
866
|
expect(results).to eq([included])
|
865
867
|
end
|
@@ -867,8 +869,8 @@ describe "an Active Record model which includes PgSearch" do
|
|
867
869
|
it "can handle terms that do not have a dmetaphone equivalent" do
|
868
870
|
term_with_blank_metaphone = "w"
|
869
871
|
|
870
|
-
included = ModelWithPgSearch.create!(:
|
871
|
-
excluded = ModelWithPgSearch.create!(:
|
872
|
+
included = ModelWithPgSearch.create!(title: 'White', content: nil)
|
873
|
+
excluded = ModelWithPgSearch.create!(title: 'Black', content: nil)
|
872
874
|
|
873
875
|
results = ModelWithPgSearch.with_dmetaphones('Wight W')
|
874
876
|
expect(results).to eq([included])
|
@@ -878,39 +880,39 @@ describe "an Active Record model which includes PgSearch" do
|
|
878
880
|
context "using multiple features" do
|
879
881
|
before do
|
880
882
|
ModelWithPgSearch.pg_search_scope :with_tsearch,
|
881
|
-
|
882
|
-
|
883
|
-
|
884
|
-
|
883
|
+
against: :title,
|
884
|
+
using: [
|
885
|
+
[:tsearch, { dictionary: 'english' }]
|
886
|
+
]
|
885
887
|
|
886
888
|
ModelWithPgSearch.pg_search_scope :with_trigram,
|
887
|
-
|
888
|
-
|
889
|
+
against: :title,
|
890
|
+
using: :trigram
|
889
891
|
|
890
892
|
ModelWithPgSearch.pg_search_scope :with_trigram_and_ignoring_accents,
|
891
|
-
|
892
|
-
|
893
|
-
|
893
|
+
against: :title,
|
894
|
+
ignoring: :accents,
|
895
|
+
using: :trigram
|
894
896
|
|
895
897
|
ModelWithPgSearch.pg_search_scope :with_tsearch_and_trigram,
|
896
|
-
|
897
|
-
|
898
|
-
|
899
|
-
|
900
|
-
|
898
|
+
against: :title,
|
899
|
+
using: [
|
900
|
+
[:tsearch, { dictionary: 'english' }],
|
901
|
+
:trigram
|
902
|
+
]
|
901
903
|
|
902
904
|
ModelWithPgSearch.pg_search_scope :complex_search,
|
903
|
-
|
904
|
-
|
905
|
-
|
906
|
-
|
907
|
-
|
908
|
-
|
909
|
-
|
905
|
+
against: %i[content title],
|
906
|
+
ignoring: :accents,
|
907
|
+
using: {
|
908
|
+
tsearch: { dictionary: 'english' },
|
909
|
+
dmetaphone: {},
|
910
|
+
trigram: {}
|
911
|
+
}
|
910
912
|
end
|
911
913
|
|
912
914
|
it "returns rows that match using any of the features" do
|
913
|
-
record = ModelWithPgSearch.create!(:
|
915
|
+
record = ModelWithPgSearch.create!(title: "tiling is grouty")
|
914
916
|
|
915
917
|
# matches trigram only
|
916
918
|
trigram_query = "ling is grouty"
|
@@ -949,21 +951,21 @@ describe "an Active Record model which includes PgSearch" do
|
|
949
951
|
|
950
952
|
context "with feature-specific configuration" do
|
951
953
|
before do
|
952
|
-
@tsearch_config = tsearch_config = {:
|
953
|
-
@trigram_config = trigram_config = {:
|
954
|
+
@tsearch_config = tsearch_config = { dictionary: 'english' }
|
955
|
+
@trigram_config = trigram_config = { foo: 'bar' }
|
954
956
|
|
955
957
|
ModelWithPgSearch.pg_search_scope :with_tsearch_and_trigram_using_hash,
|
956
|
-
|
957
|
-
|
958
|
-
|
959
|
-
|
960
|
-
|
958
|
+
against: :title,
|
959
|
+
using: {
|
960
|
+
tsearch: tsearch_config,
|
961
|
+
trigram: trigram_config
|
962
|
+
}
|
961
963
|
end
|
962
964
|
|
963
965
|
it "should pass the custom configuration down to the specified feature" do
|
964
966
|
stub_feature = double(
|
965
|
-
:
|
966
|
-
:
|
967
|
+
conditions: Arel::Nodes::Grouping.new(Arel.sql("1 = 1")),
|
968
|
+
rank: Arel::Nodes::Grouping.new(Arel.sql("1.0"))
|
967
969
|
)
|
968
970
|
|
969
971
|
expect(PgSearch::Features::TSearch).to receive(:new).with(anything, @tsearch_config, anything, anything, anything).at_least(:once).and_return(stub_feature)
|
@@ -1011,13 +1013,13 @@ describe "an Active Record model which includes PgSearch" do
|
|
1011
1013
|
unexpected.comments.create(body: 'commentwo')
|
1012
1014
|
|
1013
1015
|
Post.pg_search_scope :search_by_content_with_tsvector,
|
1014
|
-
|
1015
|
-
|
1016
|
-
|
1017
|
-
|
1018
|
-
|
1019
|
-
|
1020
|
-
|
1016
|
+
associated_against: { comments: [:body] },
|
1017
|
+
using: {
|
1018
|
+
tsearch: {
|
1019
|
+
tsvector_column: 'content_tsvector',
|
1020
|
+
dictionary: 'english'
|
1021
|
+
}
|
1022
|
+
}
|
1021
1023
|
end
|
1022
1024
|
|
1023
1025
|
it "should find by the tsvector column" do
|
@@ -1039,13 +1041,13 @@ describe "an Active Record model which includes PgSearch" do
|
|
1039
1041
|
include PgSearch
|
1040
1042
|
|
1041
1043
|
pg_search_scope :search_by_multiple_tsvector_columns,
|
1042
|
-
|
1043
|
-
|
1044
|
-
|
1045
|
-
|
1046
|
-
|
1047
|
-
|
1048
|
-
|
1044
|
+
against: ['content', 'message'],
|
1045
|
+
using: {
|
1046
|
+
tsearch: {
|
1047
|
+
tsvector_column: ['content_tsvector', 'message_tsvector'],
|
1048
|
+
dictionary: 'english'
|
1049
|
+
}
|
1050
|
+
}
|
1049
1051
|
end
|
1050
1052
|
end
|
1051
1053
|
|
@@ -1067,8 +1069,8 @@ describe "an Active Record model which includes PgSearch" do
|
|
1067
1069
|
model { include PgSearch }
|
1068
1070
|
end
|
1069
1071
|
|
1070
|
-
let!(:expected) { ModelWithTsvector.create!(:
|
1071
|
-
let!(:unexpected) { ModelWithTsvector.create!(:
|
1072
|
+
let!(:expected) { ModelWithTsvector.create!(content: 'tiling is grouty') }
|
1073
|
+
let!(:unexpected) { ModelWithTsvector.create!(content: 'longcat is looooooooong') }
|
1072
1074
|
|
1073
1075
|
before do
|
1074
1076
|
ActiveRecord::Base.connection.execute <<-SQL.strip_heredoc
|
@@ -1077,13 +1079,13 @@ describe "an Active Record model which includes PgSearch" do
|
|
1077
1079
|
SQL
|
1078
1080
|
|
1079
1081
|
ModelWithTsvector.pg_search_scope :search_by_content_with_tsvector,
|
1080
|
-
|
1081
|
-
|
1082
|
-
|
1083
|
-
|
1084
|
-
|
1085
|
-
|
1086
|
-
|
1082
|
+
against: :content,
|
1083
|
+
using: {
|
1084
|
+
tsearch: {
|
1085
|
+
tsvector_column: 'content_tsvector',
|
1086
|
+
dictionary: 'english'
|
1087
|
+
}
|
1088
|
+
}
|
1087
1089
|
end
|
1088
1090
|
|
1089
1091
|
it "should not use to_tsvector in the query" do
|
@@ -1117,43 +1119,52 @@ describe "an Active Record model which includes PgSearch" do
|
|
1117
1119
|
context "ignoring accents" do
|
1118
1120
|
before do
|
1119
1121
|
ModelWithPgSearch.pg_search_scope :search_title_without_accents,
|
1120
|
-
|
1121
|
-
|
1122
|
+
against: :title,
|
1123
|
+
ignoring: :accents
|
1122
1124
|
end
|
1123
1125
|
|
1124
1126
|
it "returns rows that match the query but not its accents" do
|
1125
1127
|
# \303\241 is a with acute accent
|
1126
1128
|
# \303\251 is e with acute accent
|
1127
1129
|
|
1128
|
-
included = ModelWithPgSearch.create!(:
|
1130
|
+
included = ModelWithPgSearch.create!(title: "\303\241bcdef")
|
1129
1131
|
|
1130
1132
|
results = ModelWithPgSearch.search_title_without_accents("abcd\303\251f")
|
1131
1133
|
expect(results).to eq([included])
|
1132
1134
|
end
|
1135
|
+
|
1136
|
+
context "when the query includes accents" do
|
1137
|
+
it "does not create an erroneous tsquery expression" do
|
1138
|
+
included = ModelWithPgSearch.create!(title: "Weird L‘Content")
|
1139
|
+
|
1140
|
+
results = ModelWithPgSearch.search_title_without_accents("L‘Content")
|
1141
|
+
expect(results).to eq([included])
|
1142
|
+
end
|
1143
|
+
end
|
1133
1144
|
end
|
1134
1145
|
|
1135
1146
|
context "when passed a :ranked_by expression" do
|
1136
1147
|
before do
|
1137
1148
|
ModelWithPgSearch.pg_search_scope :search_content_with_default_rank,
|
1138
|
-
|
1149
|
+
against: :content
|
1139
1150
|
|
1140
1151
|
ModelWithPgSearch.pg_search_scope :search_content_with_importance_as_rank,
|
1141
|
-
|
1142
|
-
|
1152
|
+
against: :content,
|
1153
|
+
ranked_by: "importance"
|
1143
1154
|
|
1144
1155
|
ModelWithPgSearch.pg_search_scope :search_content_with_importance_as_rank_multiplier,
|
1145
|
-
|
1146
|
-
|
1156
|
+
against: :content,
|
1157
|
+
ranked_by: ":tsearch * importance"
|
1147
1158
|
end
|
1148
1159
|
|
1149
1160
|
it "should return records with a rank attribute equal to the :ranked_by expression" do
|
1150
|
-
ModelWithPgSearch.create!(:
|
1161
|
+
ModelWithPgSearch.create!(content: 'foo', importance: 10)
|
1151
1162
|
results = ModelWithPgSearch.search_content_with_importance_as_rank("foo").with_pg_search_rank
|
1152
1163
|
expect(results.first.pg_search_rank).to eq(10)
|
1153
1164
|
end
|
1154
1165
|
|
1155
1166
|
it "should substitute :tsearch with the tsearch rank expression in the :ranked_by expression" do
|
1156
|
-
ModelWithPgSearch.create!(:
|
1167
|
+
ModelWithPgSearch.create!(content: 'foo', importance: 10)
|
1157
1168
|
|
1158
1169
|
tsearch_result =
|
1159
1170
|
ModelWithPgSearch.search_content_with_default_rank("foo").with_pg_search_rank.first
|
@@ -1162,8 +1173,8 @@ describe "an Active Record model which includes PgSearch" do
|
|
1162
1173
|
|
1163
1174
|
multiplied_result =
|
1164
1175
|
ModelWithPgSearch.search_content_with_importance_as_rank_multiplier("foo")
|
1165
|
-
|
1166
|
-
|
1176
|
+
.with_pg_search_rank
|
1177
|
+
.first
|
1167
1178
|
|
1168
1179
|
multiplied_rank = multiplied_result.pg_search_rank
|
1169
1180
|
|
@@ -1172,9 +1183,9 @@ describe "an Active Record model which includes PgSearch" do
|
|
1172
1183
|
|
1173
1184
|
it "should return results in descending order of the value of the rank expression" do
|
1174
1185
|
records = [
|
1175
|
-
ModelWithPgSearch.create!(:
|
1176
|
-
ModelWithPgSearch.create!(:
|
1177
|
-
ModelWithPgSearch.create!(:
|
1186
|
+
ModelWithPgSearch.create!(content: 'foo', importance: 1),
|
1187
|
+
ModelWithPgSearch.create!(content: 'foo', importance: 3),
|
1188
|
+
ModelWithPgSearch.create!(content: 'foo', importance: 2)
|
1178
1189
|
]
|
1179
1190
|
|
1180
1191
|
results = ModelWithPgSearch.search_content_with_importance_as_rank("foo")
|
@@ -1186,10 +1197,10 @@ describe "an Active Record model which includes PgSearch" do
|
|
1186
1197
|
let(:scope_name) { :"search_content_ranked_by_#{feature}" }
|
1187
1198
|
before do
|
1188
1199
|
ModelWithPgSearch.pg_search_scope scope_name,
|
1189
|
-
|
1190
|
-
|
1200
|
+
against: :content,
|
1201
|
+
ranked_by: ":#{feature}"
|
1191
1202
|
|
1192
|
-
ModelWithPgSearch.create!(:
|
1203
|
+
ModelWithPgSearch.create!(content: 'foo')
|
1193
1204
|
end
|
1194
1205
|
|
1195
1206
|
context "when .with_pg_search_rank is chained after" do
|
@@ -1223,12 +1234,12 @@ describe "an Active Record model which includes PgSearch" do
|
|
1223
1234
|
context "using the tsearch ranking algorithm" do
|
1224
1235
|
it "sorts results by the tsearch rank" do
|
1225
1236
|
ModelWithPgSearch.pg_search_scope :search_content_ranked_by_tsearch,
|
1226
|
-
|
1227
|
-
|
1228
|
-
|
1237
|
+
using: :tsearch,
|
1238
|
+
against: :content,
|
1239
|
+
ranked_by: ":tsearch"
|
1229
1240
|
|
1230
|
-
once = ModelWithPgSearch.create!(:
|
1231
|
-
twice = ModelWithPgSearch.create!(:
|
1241
|
+
once = ModelWithPgSearch.create!(content: 'foo bar')
|
1242
|
+
twice = ModelWithPgSearch.create!(content: 'foo foo')
|
1232
1243
|
|
1233
1244
|
results = ModelWithPgSearch.search_content_ranked_by_tsearch('foo')
|
1234
1245
|
expect(results.find_index(twice)).to be < results.find_index(once)
|
@@ -1238,12 +1249,12 @@ describe "an Active Record model which includes PgSearch" do
|
|
1238
1249
|
context "using the trigram ranking algorithm" do
|
1239
1250
|
it "sorts results by the trigram rank" do
|
1240
1251
|
ModelWithPgSearch.pg_search_scope :search_content_ranked_by_trigram,
|
1241
|
-
|
1242
|
-
|
1243
|
-
|
1252
|
+
using: :trigram,
|
1253
|
+
against: :content,
|
1254
|
+
ranked_by: ":trigram"
|
1244
1255
|
|
1245
|
-
close = ModelWithPgSearch.create!(:
|
1246
|
-
exact = ModelWithPgSearch.create!(:
|
1256
|
+
close = ModelWithPgSearch.create!(content: 'abcdef')
|
1257
|
+
exact = ModelWithPgSearch.create!(content: 'abc')
|
1247
1258
|
|
1248
1259
|
results = ModelWithPgSearch.search_content_ranked_by_trigram('abc')
|
1249
1260
|
expect(results.find_index(exact)).to be < results.find_index(close)
|
@@ -1253,12 +1264,12 @@ describe "an Active Record model which includes PgSearch" do
|
|
1253
1264
|
context "using the dmetaphone ranking algorithm" do
|
1254
1265
|
it "sorts results by the dmetaphone rank" do
|
1255
1266
|
ModelWithPgSearch.pg_search_scope :search_content_ranked_by_dmetaphone,
|
1256
|
-
|
1257
|
-
|
1258
|
-
|
1267
|
+
using: :dmetaphone,
|
1268
|
+
against: :content,
|
1269
|
+
ranked_by: ":dmetaphone"
|
1259
1270
|
|
1260
|
-
once = ModelWithPgSearch.create!(:
|
1261
|
-
twice = ModelWithPgSearch.create!(:
|
1271
|
+
once = ModelWithPgSearch.create!(content: 'Phoo Bar')
|
1272
|
+
twice = ModelWithPgSearch.create!(content: 'Phoo Fu')
|
1262
1273
|
|
1263
1274
|
results = ModelWithPgSearch.search_content_ranked_by_dmetaphone('foo')
|
1264
1275
|
expect(results.find_index(twice)).to be < results.find_index(once)
|
@@ -1269,17 +1280,17 @@ describe "an Active Record model which includes PgSearch" do
|
|
1269
1280
|
context "when there is a sort only feature" do
|
1270
1281
|
it "excludes that feature from the conditions, but uses it in the sorting" do
|
1271
1282
|
ModelWithPgSearch.pg_search_scope :search_content_ranked_by_dmetaphone,
|
1272
|
-
|
1273
|
-
|
1274
|
-
|
1275
|
-
|
1276
|
-
|
1277
|
-
|
1278
|
-
|
1279
|
-
exact = ModelWithPgSearch.create!(:
|
1280
|
-
one_exact_one_close = ModelWithPgSearch.create!(:
|
1281
|
-
one_exact = ModelWithPgSearch.create!(:
|
1282
|
-
one_close = ModelWithPgSearch.create!(:
|
1283
|
+
against: :content,
|
1284
|
+
using: {
|
1285
|
+
tsearch: { any_word: true, prefix: true },
|
1286
|
+
dmetaphone: { any_word: true, prefix: true, sort_only: true }
|
1287
|
+
},
|
1288
|
+
ranked_by: ":tsearch + (0.5 * :dmetaphone)"
|
1289
|
+
|
1290
|
+
exact = ModelWithPgSearch.create!(content: "ash hines")
|
1291
|
+
one_exact_one_close = ModelWithPgSearch.create!(content: "ash heinz")
|
1292
|
+
one_exact = ModelWithPgSearch.create!(content: "ash smith")
|
1293
|
+
one_close = ModelWithPgSearch.create!(content: "leigh heinz")
|
1283
1294
|
|
1284
1295
|
results = ModelWithPgSearch.search_content_ranked_by_dmetaphone("ash hines")
|
1285
1296
|
expect(results).to eq [exact, one_exact_one_close, one_exact]
|