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