pg_search 2.3.7 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +38 -37
- data/.github/workflows/codeql.yml +86 -0
- data/.gitignore +0 -1
- data/.standard.yml +2 -2
- data/CHANGELOG.md +81 -64
- data/CONTRIBUTING.md +1 -1
- data/Gemfile +4 -2
- data/LICENSE +1 -1
- data/README.md +89 -87
- data/Rakefile +1 -7
- data/doc/plans/2026-09-05-arel-stack.md +43 -0
- data/doc/plans/2026-09-06-arel-remaining-expressions.md +48 -0
- data/lib/pg_search/configuration/association.rb +37 -23
- data/lib/pg_search/configuration/column.rb +17 -12
- data/lib/pg_search/configuration/foreign_column.rb +3 -5
- data/lib/pg_search/configuration.rb +10 -26
- data/lib/pg_search/features/dmetaphone.rb +3 -7
- data/lib/pg_search/features/feature.rb +8 -4
- data/lib/pg_search/features/trigram.rb +6 -20
- data/lib/pg_search/features/tsearch.rb +47 -61
- data/lib/pg_search/features.rb +2 -0
- data/lib/pg_search/migration/generator.rb +1 -5
- data/lib/pg_search/model.rb +6 -8
- data/lib/pg_search/multisearch/rebuilder.rb +69 -64
- data/lib/pg_search/normalizer.rb +40 -11
- data/lib/pg_search/scope_options.rb +32 -49
- data/lib/pg_search/version.rb +1 -1
- data/pg_search.gemspec +5 -5
- data/spec/integration/associations_spec.rb +133 -0
- data/spec/integration/deprecation_spec.rb +5 -1
- data/spec/integration/pagination_spec.rb +1 -0
- data/spec/integration/pg_search_spec.rb +78 -1
- data/spec/integration/single_table_inheritance_spec.rb +2 -0
- data/spec/lib/pg_search/configuration/association_spec.rb +34 -58
- data/spec/lib/pg_search/configuration/column_spec.rb +73 -15
- data/spec/lib/pg_search/configuration/foreign_column_spec.rb +53 -20
- data/spec/lib/pg_search/features/dmetaphone_spec.rb +2 -2
- data/spec/lib/pg_search/features/trigram_spec.rb +8 -9
- data/spec/lib/pg_search/features/tsearch_spec.rb +8 -8
- data/spec/lib/pg_search/multisearch/rebuilder_spec.rb +251 -211
- data/spec/lib/pg_search/multisearch_spec.rb +16 -16
- data/spec/lib/pg_search/multisearchable_spec.rb +8 -0
- data/spec/lib/pg_search/normalizer_spec.rb +64 -11
- data/spec/lib/pg_search_spec.rb +6 -21
- data/spec/spec_helper.rb +0 -13
- metadata +11 -15
- data/spec/.rubocop.yml +0 -27
- data/spec/integration/.rubocop.yml +0 -11
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "active_support/core_ext/module/delegation"
|
|
4
|
-
|
|
5
3
|
module PgSearch
|
|
6
4
|
class ScopeOptions
|
|
7
5
|
attr_reader :config, :feature_options, :model
|
|
@@ -15,10 +13,12 @@ module PgSearch
|
|
|
15
13
|
def apply(scope)
|
|
16
14
|
scope = include_table_aliasing_for_rank(scope)
|
|
17
15
|
rank_table_alias = scope.pg_search_rank_table_alias(include_counter: true)
|
|
16
|
+
rank_subquery = subquery.arel.as(rank_table_alias)
|
|
18
17
|
|
|
19
18
|
scope
|
|
20
|
-
.joins(rank_join(
|
|
21
|
-
.order(
|
|
19
|
+
.joins(rank_join(rank_subquery))
|
|
20
|
+
.order(rank_subquery[:rank].desc)
|
|
21
|
+
.order(order_within_rank)
|
|
22
22
|
.extend(WithPgSearchRank)
|
|
23
23
|
.extend(WithPgSearchHighlight[feature_for(:tsearch)])
|
|
24
24
|
end
|
|
@@ -27,6 +27,7 @@ module PgSearch
|
|
|
27
27
|
def self.[](tsearch)
|
|
28
28
|
Module.new do
|
|
29
29
|
include WithPgSearchHighlight
|
|
30
|
+
|
|
30
31
|
define_method(:tsearch) { tsearch }
|
|
31
32
|
end
|
|
32
33
|
end
|
|
@@ -37,20 +38,18 @@ module PgSearch
|
|
|
37
38
|
|
|
38
39
|
def with_pg_search_highlight
|
|
39
40
|
scope = self
|
|
40
|
-
scope = scope.select(
|
|
41
|
-
scope.select("
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
def highlight
|
|
45
|
-
tsearch.highlight.to_sql
|
|
41
|
+
scope = scope.select(arel_table[Arel.star]) if scope.select_values.empty?
|
|
42
|
+
scope.select(tsearch.highlight.as("pg_search_highlight"))
|
|
46
43
|
end
|
|
47
44
|
end
|
|
48
45
|
|
|
49
46
|
module WithPgSearchRank
|
|
50
47
|
def with_pg_search_rank
|
|
51
48
|
scope = self
|
|
52
|
-
scope = scope.select(
|
|
53
|
-
|
|
49
|
+
scope = scope.select(arel_table[Arel.star]) if scope.select_values.empty?
|
|
50
|
+
rank_table = arel_table.alias(pg_search_rank_table_alias)
|
|
51
|
+
rank_column = Arel::Attributes::Attribute.new(rank_table, "rank")
|
|
52
|
+
scope.select(rank_column.as("pg_search_rank"))
|
|
54
53
|
end
|
|
55
54
|
end
|
|
56
55
|
|
|
@@ -76,13 +75,11 @@ module PgSearch
|
|
|
76
75
|
|
|
77
76
|
private
|
|
78
77
|
|
|
79
|
-
delegate :connection, :quoted_table_name, to: :model
|
|
80
|
-
|
|
81
78
|
def subquery
|
|
82
79
|
model
|
|
83
80
|
.unscoped
|
|
84
|
-
.select("
|
|
85
|
-
.select("
|
|
81
|
+
.select(model.arel_table[model.primary_key].as("pg_search_id"))
|
|
82
|
+
.select(rank.as("rank"))
|
|
86
83
|
.joins(subquery_join)
|
|
87
84
|
.where(conditions)
|
|
88
85
|
.limit(nil)
|
|
@@ -95,45 +92,26 @@ module PgSearch
|
|
|
95
92
|
.reject { |_feature_name, feature_options| feature_options && feature_options[:sort_only] }
|
|
96
93
|
.map { |feature_name, _feature_options| feature_for(feature_name).conditions }
|
|
97
94
|
|
|
98
|
-
|
|
95
|
+
Arel::Nodes::Or.new(expressions)
|
|
99
96
|
end
|
|
100
97
|
|
|
101
|
-
# https://github.com/rails/rails/pull/51492
|
|
102
|
-
# :nocov:
|
|
103
|
-
# standard:disable Lint/DuplicateMethods
|
|
104
|
-
or_arity = Arel::Nodes::Or.instance_method(:initialize).arity
|
|
105
|
-
case or_arity
|
|
106
|
-
when 1
|
|
107
|
-
def or_node(expressions)
|
|
108
|
-
Arel::Nodes::Or.new(expressions)
|
|
109
|
-
end
|
|
110
|
-
when 2
|
|
111
|
-
def or_node(expressions)
|
|
112
|
-
expressions.inject { |accumulator, expression| Arel::Nodes::Or.new(accumulator, expression) }
|
|
113
|
-
end
|
|
114
|
-
else
|
|
115
|
-
raise "Unsupported arity #{or_arity} for Arel::Nodes::Or#initialize"
|
|
116
|
-
end
|
|
117
|
-
# :nocov:
|
|
118
|
-
# standard:enable Lint/DuplicateMethods
|
|
119
|
-
|
|
120
98
|
def order_within_rank
|
|
121
|
-
config.order_within_rank
|
|
99
|
+
if config.order_within_rank
|
|
100
|
+
Arel.sql(config.order_within_rank)
|
|
101
|
+
else
|
|
102
|
+
model.arel_table[model.primary_key].asc
|
|
103
|
+
end
|
|
122
104
|
end
|
|
123
105
|
|
|
124
106
|
def primary_key
|
|
125
|
-
|
|
107
|
+
Arel::Attributes::Attribute.new(model.arel_table, model.primary_key)
|
|
126
108
|
end
|
|
127
109
|
|
|
128
110
|
def subquery_join
|
|
129
|
-
|
|
130
|
-
config.associations.map do |association|
|
|
131
|
-
association.join(primary_key)
|
|
132
|
-
end.join(" ")
|
|
133
|
-
end
|
|
111
|
+
config.associations.map { |association| association.to_arel(primary_key) }
|
|
134
112
|
end
|
|
135
113
|
|
|
136
|
-
FEATURE_CLASSES = {
|
|
114
|
+
FEATURE_CLASSES = { # standard:disable Lint/UselessConstantScoping
|
|
137
115
|
dmetaphone: Features::DMetaphone,
|
|
138
116
|
tsearch: Features::TSearch,
|
|
139
117
|
trigram: Features::Trigram
|
|
@@ -157,17 +135,22 @@ module PgSearch
|
|
|
157
135
|
end
|
|
158
136
|
|
|
159
137
|
def rank
|
|
160
|
-
(
|
|
138
|
+
return feature_for(:tsearch).rank unless config.ranking_sql
|
|
139
|
+
|
|
140
|
+
Arel.sql(config.ranking_sql.gsub(/:(\w*)/) do
|
|
161
141
|
feature_for(Regexp.last_match(1)).rank.to_sql
|
|
162
|
-
end
|
|
142
|
+
end)
|
|
163
143
|
end
|
|
164
144
|
|
|
165
|
-
def rank_join(
|
|
166
|
-
|
|
145
|
+
def rank_join(rank_subquery)
|
|
146
|
+
model.arel_table
|
|
147
|
+
.join(rank_subquery)
|
|
148
|
+
.on(model.arel_table[model.primary_key].eq(rank_subquery[:pg_search_id]))
|
|
149
|
+
.join_sources
|
|
167
150
|
end
|
|
168
151
|
|
|
169
152
|
def include_table_aliasing_for_rank(scope)
|
|
170
|
-
return scope if scope.
|
|
153
|
+
return scope if scope.include?(PgSearchRankTableAliasing)
|
|
171
154
|
|
|
172
155
|
scope.all.spawn.tap do |new_scope|
|
|
173
156
|
new_scope.instance_eval { extend PgSearchRankTableAliasing }
|
data/lib/pg_search/version.rb
CHANGED
data/pg_search.gemspec
CHANGED
|
@@ -7,8 +7,8 @@ Gem::Specification.new do |s|
|
|
|
7
7
|
s.name = "pg_search"
|
|
8
8
|
s.version = PgSearch::VERSION
|
|
9
9
|
s.platform = Gem::Platform::RUBY
|
|
10
|
-
s.authors = ["Grant Hutchins"
|
|
11
|
-
s.email =
|
|
10
|
+
s.authors = ["Grant Hutchins"]
|
|
11
|
+
s.email = ["gems@nertzy.com"]
|
|
12
12
|
s.homepage = "https://github.com/Casecommons/pg_search"
|
|
13
13
|
s.summary = "PgSearch builds Active Record named scopes that take advantage of PostgreSQL's full text search"
|
|
14
14
|
s.description = "PgSearch builds Active Record named scopes that take advantage of PostgreSQL's full text search"
|
|
@@ -18,8 +18,8 @@ Gem::Specification.new do |s|
|
|
|
18
18
|
s.files = `git ls-files -z`.split("\x0")
|
|
19
19
|
s.require_paths = ["lib"]
|
|
20
20
|
|
|
21
|
-
s.add_dependency "activerecord", ">=
|
|
22
|
-
s.add_dependency "activesupport", ">=
|
|
21
|
+
s.add_dependency "activerecord", ">= 8.0"
|
|
22
|
+
s.add_dependency "activesupport", ">= 8.0"
|
|
23
23
|
|
|
24
|
-
s.required_ruby_version = ">= 3.
|
|
24
|
+
s.required_ruby_version = ">= 3.3"
|
|
25
25
|
end
|
|
@@ -20,6 +20,7 @@ describe "a pg_search_scope" do
|
|
|
20
20
|
|
|
21
21
|
model do
|
|
22
22
|
include PgSearch::Model
|
|
23
|
+
|
|
23
24
|
belongs_to :another_model, class_name: "AssociatedModel"
|
|
24
25
|
|
|
25
26
|
pg_search_scope :with_another, associated_against: {another_model: :title}
|
|
@@ -57,6 +58,7 @@ describe "a pg_search_scope" do
|
|
|
57
58
|
|
|
58
59
|
model do
|
|
59
60
|
include PgSearch::Model
|
|
61
|
+
|
|
60
62
|
belongs_to :another_model, class_name: "AssociatedModel"
|
|
61
63
|
|
|
62
64
|
pg_search_scope :with_associated, against: :title, associated_against: {another_model: :title}
|
|
@@ -93,6 +95,7 @@ describe "a pg_search_scope" do
|
|
|
93
95
|
|
|
94
96
|
model do
|
|
95
97
|
include PgSearch::Model
|
|
98
|
+
|
|
96
99
|
has_many :other_models, class_name: "AssociatedModelWithHasMany", foreign_key: "ModelWithHasMany_id"
|
|
97
100
|
|
|
98
101
|
pg_search_scope :with_associated, against: [:title], associated_against: {other_models: :title}
|
|
@@ -142,6 +145,127 @@ describe "a pg_search_scope" do
|
|
|
142
145
|
end
|
|
143
146
|
end
|
|
144
147
|
|
|
148
|
+
context "via a scoped has_many association with a quoted literal condition" do
|
|
149
|
+
with_model :Book do
|
|
150
|
+
table do |t|
|
|
151
|
+
t.string "title"
|
|
152
|
+
t.string "author"
|
|
153
|
+
t.belongs_to :shelf
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
model do
|
|
157
|
+
belongs_to :shelf
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
with_model :Shelf do
|
|
162
|
+
table do |t|
|
|
163
|
+
t.string "title"
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
model do
|
|
167
|
+
include PgSearch::Model
|
|
168
|
+
|
|
169
|
+
has_many :obrien_books,
|
|
170
|
+
-> { where(author: "O'Brien") },
|
|
171
|
+
class_name: "Book"
|
|
172
|
+
|
|
173
|
+
pg_search_scope :with_obrien_books,
|
|
174
|
+
associated_against: {obrien_books: :title}
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
it "preserves scoped values through searches" do
|
|
179
|
+
shelf_with_obrien = Shelf.create!(title: "Main Shelf")
|
|
180
|
+
shelf_without_obrien = Shelf.create!(title: "Other Shelf")
|
|
181
|
+
|
|
182
|
+
Book.create!(
|
|
183
|
+
title: "The Third Policeman",
|
|
184
|
+
author: "O'Brien",
|
|
185
|
+
shelf: shelf_with_obrien
|
|
186
|
+
)
|
|
187
|
+
Book.create!(
|
|
188
|
+
title: "The Third Policeman",
|
|
189
|
+
author: "Someone Else",
|
|
190
|
+
shelf: shelf_without_obrien
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
results = Shelf.with_obrien_books("Third Policeman")
|
|
194
|
+
expect(results).to include(shelf_with_obrien)
|
|
195
|
+
expect(results).not_to include(shelf_without_obrien)
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
context "with a physical source column shadowed by a model alias" do
|
|
200
|
+
with_model :Book do
|
|
201
|
+
table do |t|
|
|
202
|
+
t.string :title
|
|
203
|
+
t.string :name
|
|
204
|
+
t.belongs_to :shelf
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
model do
|
|
208
|
+
belongs_to :shelf
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
with_model :Shelf do
|
|
213
|
+
table
|
|
214
|
+
|
|
215
|
+
model do
|
|
216
|
+
include PgSearch::Model
|
|
217
|
+
|
|
218
|
+
has_many :books
|
|
219
|
+
|
|
220
|
+
pg_search_scope :search, associated_against: {books: :title}
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
it "searches the physical association source through the derived alias" do
|
|
225
|
+
shelf = Shelf.create!
|
|
226
|
+
shelf.books.create!(title: "physical", name: "alias")
|
|
227
|
+
Book.alias_attribute :title, :name
|
|
228
|
+
|
|
229
|
+
expect(Shelf.search("physical")).to eq [shelf]
|
|
230
|
+
expect(Shelf.search("alias")).to be_empty
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
context "with a trusted SQL expression as the association source" do
|
|
235
|
+
with_model :Book do
|
|
236
|
+
table do |t|
|
|
237
|
+
t.json :metadata
|
|
238
|
+
t.belongs_to :shelf
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
model do
|
|
242
|
+
belongs_to :shelf
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
with_model :Shelf do
|
|
247
|
+
table
|
|
248
|
+
|
|
249
|
+
model do
|
|
250
|
+
include PgSearch::Model
|
|
251
|
+
|
|
252
|
+
has_many :books
|
|
253
|
+
|
|
254
|
+
pg_search_scope :search,
|
|
255
|
+
associated_against: {books: Arel.sql("metadata->>'title'")}
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
it "evaluates the expression before aggregating the associated rows" do
|
|
260
|
+
shelf = Shelf.create!
|
|
261
|
+
shelf.books.create!(metadata: {title: "Hello"})
|
|
262
|
+
shelf.books.create!(metadata: {title: "world"})
|
|
263
|
+
|
|
264
|
+
expect(Shelf.search("Hello world")).to eq [shelf]
|
|
265
|
+
expect(Shelf.search("title")).to be_empty
|
|
266
|
+
end
|
|
267
|
+
end
|
|
268
|
+
|
|
145
269
|
context "when across multiple associations" do
|
|
146
270
|
context "when on different tables" do
|
|
147
271
|
with_model :FirstAssociatedModel do
|
|
@@ -284,6 +408,7 @@ describe "a pg_search_scope" do
|
|
|
284
408
|
|
|
285
409
|
model do
|
|
286
410
|
include PgSearch::Model
|
|
411
|
+
|
|
287
412
|
belongs_to :another_model, class_name: "AssociatedModel"
|
|
288
413
|
|
|
289
414
|
pg_search_scope :with_associated, associated_against: {another_model: %i[title author]}
|
|
@@ -337,6 +462,7 @@ describe "a pg_search_scope" do
|
|
|
337
462
|
|
|
338
463
|
model do
|
|
339
464
|
include PgSearch::Model
|
|
465
|
+
|
|
340
466
|
belongs_to :another_model, class_name: "AssociatedModel"
|
|
341
467
|
|
|
342
468
|
pg_search_scope :with_associated, associated_against: {another_model: :number}
|
|
@@ -368,6 +494,7 @@ describe "a pg_search_scope" do
|
|
|
368
494
|
model do
|
|
369
495
|
has_many :children
|
|
370
496
|
include PgSearch::Model
|
|
497
|
+
|
|
371
498
|
pg_search_scope :search_name, against: :name
|
|
372
499
|
end
|
|
373
500
|
end
|
|
@@ -398,6 +525,8 @@ describe "a pg_search_scope" do
|
|
|
398
525
|
|
|
399
526
|
context "when merging a pg_search_scope into another model's scope" do
|
|
400
527
|
with_model :ModelWithAssociation do
|
|
528
|
+
table
|
|
529
|
+
|
|
401
530
|
model do
|
|
402
531
|
has_many :associated_models
|
|
403
532
|
end
|
|
@@ -411,6 +540,7 @@ describe "a pg_search_scope" do
|
|
|
411
540
|
|
|
412
541
|
model do
|
|
413
542
|
include PgSearch::Model
|
|
543
|
+
|
|
414
544
|
belongs_to :model_with_association
|
|
415
545
|
|
|
416
546
|
pg_search_scope :search_content, against: :content
|
|
@@ -444,6 +574,8 @@ describe "a pg_search_scope" do
|
|
|
444
574
|
|
|
445
575
|
context "when chained onto a has_many association" do
|
|
446
576
|
with_model :Company do
|
|
577
|
+
table
|
|
578
|
+
|
|
447
579
|
model do
|
|
448
580
|
has_many :positions
|
|
449
581
|
end
|
|
@@ -457,6 +589,7 @@ describe "a pg_search_scope" do
|
|
|
457
589
|
|
|
458
590
|
model do
|
|
459
591
|
include PgSearch::Model
|
|
592
|
+
|
|
460
593
|
pg_search_scope :search, against: :title, using: %i[tsearch trigram]
|
|
461
594
|
end
|
|
462
595
|
end
|
|
@@ -5,6 +5,8 @@ require "active_support/core_ext/kernel/reporting"
|
|
|
5
5
|
|
|
6
6
|
describe "Including the deprecated PgSearch module" do
|
|
7
7
|
with_model :SomeModel do
|
|
8
|
+
table
|
|
9
|
+
|
|
8
10
|
model do
|
|
9
11
|
silence_warnings do
|
|
10
12
|
include PgSearch
|
|
@@ -12,7 +14,9 @@ describe "Including the deprecated PgSearch module" do
|
|
|
12
14
|
end
|
|
13
15
|
end
|
|
14
16
|
|
|
15
|
-
with_model :AnotherModel
|
|
17
|
+
with_model :AnotherModel do
|
|
18
|
+
table
|
|
19
|
+
end
|
|
16
20
|
|
|
17
21
|
it "includes PgSearch::Model" do
|
|
18
22
|
expect(SomeModel.ancestors).to include PgSearch::Model
|
|
@@ -14,6 +14,7 @@ describe "an Active Record model which includes PgSearch" do
|
|
|
14
14
|
|
|
15
15
|
model do
|
|
16
16
|
include PgSearch::Model
|
|
17
|
+
|
|
17
18
|
belongs_to :parent_model
|
|
18
19
|
end
|
|
19
20
|
end
|
|
@@ -24,6 +25,7 @@ describe "an Active Record model which includes PgSearch" do
|
|
|
24
25
|
|
|
25
26
|
model do
|
|
26
27
|
include PgSearch::Model
|
|
28
|
+
|
|
27
29
|
has_many :models_with_pg_search
|
|
28
30
|
scope :active, -> { where(active: true) }
|
|
29
31
|
end
|
|
@@ -238,6 +240,7 @@ describe "an Active Record model which includes PgSearch" do
|
|
|
238
240
|
|
|
239
241
|
model do
|
|
240
242
|
include PgSearch::Model
|
|
243
|
+
|
|
241
244
|
belongs_to :person
|
|
242
245
|
pg_search_scope :search_city, against: [:city]
|
|
243
246
|
end
|
|
@@ -250,6 +253,7 @@ describe "an Active Record model which includes PgSearch" do
|
|
|
250
253
|
|
|
251
254
|
model do
|
|
252
255
|
include PgSearch::Model
|
|
256
|
+
|
|
253
257
|
has_many :houses
|
|
254
258
|
pg_search_scope :named, against: [:name]
|
|
255
259
|
scope :with_house_in_city, lambda { |city|
|
|
@@ -1046,6 +1050,7 @@ describe "an Active Record model which includes PgSearch" do
|
|
|
1046
1050
|
|
|
1047
1051
|
model do
|
|
1048
1052
|
include PgSearch::Model
|
|
1053
|
+
|
|
1049
1054
|
has_many :comments
|
|
1050
1055
|
end
|
|
1051
1056
|
end
|
|
@@ -1083,10 +1088,65 @@ describe "an Active Record model which includes PgSearch" do
|
|
|
1083
1088
|
it "finds by a combination of the two" do
|
|
1084
1089
|
expect(Post.search_by_content_with_tsvector("phooey commentone").map(&:id)).to eq([expected.id])
|
|
1085
1090
|
end
|
|
1091
|
+
|
|
1092
|
+
it "finds by the tsvector column when ordering by an associated table" do
|
|
1093
|
+
# Reference the physical table name that Active Record would normally
|
|
1094
|
+
# leave unaliased. The old string rank join made AliasTracker think the
|
|
1095
|
+
# name was already used and alias this outer join, breaking the order.
|
|
1096
|
+
# https://github.com/Casecommons/pg_search/issues/206
|
|
1097
|
+
results = Post
|
|
1098
|
+
.joins(:comments)
|
|
1099
|
+
.search_by_content_with_tsvector("phooey")
|
|
1100
|
+
.order(Comment.table_name => {id: :asc})
|
|
1101
|
+
|
|
1102
|
+
expect(results).to eq([expected])
|
|
1103
|
+
end
|
|
1104
|
+
|
|
1105
|
+
it "finds by the tsvector column when ordering by an association alias" do
|
|
1106
|
+
results = Post
|
|
1107
|
+
.joins(:comments)
|
|
1108
|
+
.search_by_content_with_tsvector("phooey")
|
|
1109
|
+
.order(comments: {id: :asc})
|
|
1110
|
+
|
|
1111
|
+
expect(results).to eq([expected])
|
|
1112
|
+
end
|
|
1113
|
+
end
|
|
1114
|
+
|
|
1115
|
+
context "with quoted identifiers and a rank attribute alias" do
|
|
1116
|
+
with_model :Article do
|
|
1117
|
+
table primary_key: "ArticleID" do |t|
|
|
1118
|
+
t.text :content
|
|
1119
|
+
t.tsvector "SearchVector"
|
|
1120
|
+
t.integer :importance
|
|
1121
|
+
end
|
|
1122
|
+
|
|
1123
|
+
model do
|
|
1124
|
+
include PgSearch::Model
|
|
1125
|
+
|
|
1126
|
+
alias_attribute :rank, :importance
|
|
1127
|
+
pg_search_scope :search_content,
|
|
1128
|
+
using: {tsearch: {tsvector_column: "SearchVector"}}
|
|
1129
|
+
end
|
|
1130
|
+
end
|
|
1131
|
+
|
|
1132
|
+
it "searches the physical vector and selects the synthetic rank" do
|
|
1133
|
+
article = Article.create!(content: "phooey", importance: 10)
|
|
1134
|
+
Article.update_all(
|
|
1135
|
+
%("SearchVector" = to_tsvector('simple', "content"))
|
|
1136
|
+
)
|
|
1137
|
+
|
|
1138
|
+
result = Article.search_content("phooey").with_pg_search_rank.first
|
|
1139
|
+
|
|
1140
|
+
expect(result).to eq(article)
|
|
1141
|
+
expect(result.pg_search_rank).to be_a(Float)
|
|
1142
|
+
expect(result.pg_search_rank).not_to eq(result.importance)
|
|
1143
|
+
end
|
|
1086
1144
|
end
|
|
1087
1145
|
|
|
1088
1146
|
context "when using multiple tsvector columns" do
|
|
1089
1147
|
with_model :ModelWithTsvector do
|
|
1148
|
+
table
|
|
1149
|
+
|
|
1090
1150
|
model do
|
|
1091
1151
|
include PgSearch::Model
|
|
1092
1152
|
|
|
@@ -1185,7 +1245,7 @@ describe "an Active Record model which includes PgSearch" do
|
|
|
1185
1245
|
end
|
|
1186
1246
|
|
|
1187
1247
|
context "when the query includes accents" do
|
|
1188
|
-
let(:term) { "L#{%w[‘ ’ ʻ ʼ].sample}Content" }
|
|
1248
|
+
let(:term) { "L#{%w[‘ ’ ʻ ʼ ʹ ʽ ˈ ' ʼn].sample}Content" }
|
|
1189
1249
|
let(:included) { ModelWithPgSearch.create!(title: "Weird #{term}") }
|
|
1190
1250
|
let(:results) { ModelWithPgSearch.search_title_without_accents(term) }
|
|
1191
1251
|
|
|
@@ -1199,6 +1259,23 @@ describe "an Active Record model which includes PgSearch" do
|
|
|
1199
1259
|
end
|
|
1200
1260
|
end
|
|
1201
1261
|
|
|
1262
|
+
context "when passed an :order_within_rank expression" do
|
|
1263
|
+
before do
|
|
1264
|
+
ModelWithPgSearch.pg_search_scope :search_content_ordered_by_importance,
|
|
1265
|
+
against: :content,
|
|
1266
|
+
order_within_rank: "importance DESC"
|
|
1267
|
+
end
|
|
1268
|
+
|
|
1269
|
+
it "breaks ties in rank by the custom expression" do
|
|
1270
|
+
# Both records match equally; importance determines order
|
|
1271
|
+
low = ModelWithPgSearch.create!(content: "foo", importance: 1)
|
|
1272
|
+
high = ModelWithPgSearch.create!(content: "foo", importance: 5)
|
|
1273
|
+
|
|
1274
|
+
results = ModelWithPgSearch.search_content_ordered_by_importance("foo")
|
|
1275
|
+
expect(results).to eq([high, low])
|
|
1276
|
+
end
|
|
1277
|
+
end
|
|
1278
|
+
|
|
1202
1279
|
context "when passed a :ranked_by expression" do
|
|
1203
1280
|
before do
|
|
1204
1281
|
ModelWithPgSearch.pg_search_scope :search_content_with_default_rank,
|
|
@@ -12,6 +12,7 @@ describe "a pg_search_scope on an STI subclass" do
|
|
|
12
12
|
|
|
13
13
|
model do
|
|
14
14
|
include PgSearch::Model
|
|
15
|
+
|
|
15
16
|
pg_search_scope :search_content, against: :content
|
|
16
17
|
end
|
|
17
18
|
end
|
|
@@ -52,6 +53,7 @@ describe "a pg_search_scope on an STI subclass" do
|
|
|
52
53
|
|
|
53
54
|
model do
|
|
54
55
|
include PgSearch::Model
|
|
56
|
+
|
|
55
57
|
self.inheritance_column = "custom_type"
|
|
56
58
|
pg_search_scope :search_content, against: :content
|
|
57
59
|
end
|