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