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