pg_search 2.3.8 → 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.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +0 -6
  3. data/.github/workflows/codeql.yml +0 -1
  4. data/.gitignore +0 -1
  5. data/.standard.yml +1 -1
  6. data/CHANGELOG.md +70 -65
  7. data/CONTRIBUTING.md +1 -1
  8. data/LICENSE +1 -1
  9. data/README.md +2 -2
  10. data/doc/plans/2026-09-05-arel-stack.md +43 -0
  11. data/doc/plans/2026-09-06-arel-remaining-expressions.md +48 -0
  12. data/lib/pg_search/configuration/association.rb +37 -23
  13. data/lib/pg_search/configuration/column.rb +17 -12
  14. data/lib/pg_search/configuration/foreign_column.rb +3 -5
  15. data/lib/pg_search/features/dmetaphone.rb +3 -7
  16. data/lib/pg_search/features/feature.rb +8 -4
  17. data/lib/pg_search/features/trigram.rb +6 -20
  18. data/lib/pg_search/features/tsearch.rb +30 -36
  19. data/lib/pg_search/features.rb +2 -0
  20. data/lib/pg_search/migration/generator.rb +1 -5
  21. data/lib/pg_search/multisearch/rebuilder.rb +69 -65
  22. data/lib/pg_search/normalizer.rb +37 -18
  23. data/lib/pg_search/scope_options.rb +28 -52
  24. data/lib/pg_search/version.rb +1 -1
  25. data/pg_search.gemspec +4 -4
  26. data/spec/integration/associations_spec.rb +121 -0
  27. data/spec/integration/pg_search_spec.rb +48 -0
  28. data/spec/lib/pg_search/configuration/association_spec.rb +32 -58
  29. data/spec/lib/pg_search/configuration/column_spec.rb +73 -15
  30. data/spec/lib/pg_search/configuration/foreign_column_spec.rb +52 -20
  31. data/spec/lib/pg_search/features/dmetaphone_spec.rb +2 -2
  32. data/spec/lib/pg_search/features/trigram_spec.rb +3 -3
  33. data/spec/lib/pg_search/features/tsearch_spec.rb +8 -8
  34. data/spec/lib/pg_search/multisearch/rebuilder_spec.rb +219 -229
  35. data/spec/lib/pg_search/multisearch_spec.rb +16 -16
  36. data/spec/lib/pg_search/normalizer_spec.rb +64 -11
  37. data/spec/lib/pg_search_spec.rb +2 -21
  38. metadata +8 -8
@@ -7,56 +7,109 @@ describe PgSearch::Normalizer do
7
7
  describe "#add_normalization" do
8
8
  context "when config[:ignore] includes :accents" do
9
9
  context "when passed an Arel node" do
10
- it "wraps the expression in unaccent()" do
10
+ it "returns an Arel node wrapping the expression in unaccent()" do
11
11
  config = instance_double(PgSearch::Configuration, "config", ignore: [:accents])
12
12
  node = Arel::Nodes::NamedFunction.new("foo", [Arel::Nodes.build_quoted("bar")])
13
13
 
14
14
  normalizer = described_class.new(config)
15
- expect(normalizer.add_normalization(node)).to eq("regexp_replace(unaccent(foo('bar')), '[''?\\:]', '', 'g')")
15
+ result = normalizer.add_normalization(node)
16
+ expect(result).to be_an(Arel::Nodes::Node)
17
+ expect(result.to_sql).to eq("regexp_replace(unaccent(foo('bar')), '[''?\\:]', '', 'g')")
16
18
  end
17
19
 
18
20
  context "when a custom unaccent function is specified" do
19
- it "wraps the expression in that function" do
21
+ it "returns an Arel node using that function" do
20
22
  allow(PgSearch).to receive(:unaccent_function).and_return("my_unaccent")
21
23
  node = Arel::Nodes::NamedFunction.new("foo", [Arel::Nodes.build_quoted("bar")])
22
24
 
23
25
  config = instance_double(PgSearch::Configuration, "config", ignore: [:accents])
24
26
 
25
27
  normalizer = described_class.new(config)
26
- expect(normalizer.add_normalization(node)).to eq("regexp_replace(my_unaccent(foo('bar')), '[''?\\:]', '', 'g')")
28
+ result = normalizer.add_normalization(node)
29
+ expect(result).to be_an(Arel::Nodes::Node)
30
+ expect(result.to_sql).to eq("regexp_replace(my_unaccent(foo('bar')), '[''?\\:]', '', 'g')")
27
31
  end
28
32
  end
29
33
  end
30
34
 
31
- context "when passed a String" do
32
- it "wraps the expression in unaccent()" do
35
+ context "when passed a String (trusted SQL expression)" do
36
+ it "returns an Arel node wrapping the expression in unaccent()" do
33
37
  config = instance_double(PgSearch::Configuration, "config", ignore: [:accents])
34
38
 
35
39
  normalizer = described_class.new(config)
36
- expect(normalizer.add_normalization("foo")).to eq("regexp_replace(unaccent(foo), '[''?\\:]', '', 'g')")
40
+ result = normalizer.add_normalization("foo")
41
+ expect(result).to be_an(Arel::Nodes::Node)
42
+ expect(result.to_sql).to eq("regexp_replace(unaccent(foo), '[''?\\:]', '', 'g')")
37
43
  end
38
44
 
39
45
  context "when a custom unaccent function is specified" do
40
- it "wraps the expression in that function" do
46
+ it "returns an Arel node using that function" do
41
47
  allow(PgSearch).to receive(:unaccent_function).and_return("my_unaccent")
42
48
 
43
49
  config = instance_double(PgSearch::Configuration, "config", ignore: [:accents])
44
50
 
45
51
  normalizer = described_class.new(config)
46
- expect(normalizer.add_normalization("foo")).to eq("regexp_replace(my_unaccent(foo), '[''?\\:]', '', 'g')")
52
+ result = normalizer.add_normalization("foo")
53
+ expect(result).to be_an(Arel::Nodes::Node)
54
+ expect(result.to_sql).to eq("regexp_replace(my_unaccent(foo), '[''?\\:]', '', 'g')")
47
55
  end
48
56
  end
49
57
  end
50
58
  end
51
59
 
60
+ it "rejects unsupported inputs instead of turning them into SQL" do
61
+ config = instance_double(PgSearch::Configuration, ignore: [])
62
+ normalizer = described_class.new(config)
63
+
64
+ [nil, Object.new, 123].each do |input|
65
+ expect { normalizer.add_normalization(input) }.to raise_error(TypeError)
66
+ end
67
+ end
68
+
52
69
  context "when config[:ignore] does not include :accents" do
53
- it "passes the expression through" do
70
+ it "passes the expression through as an Arel-compatible object" do
54
71
  config = instance_double(PgSearch::Configuration, "config", ignore: [])
55
72
 
56
73
  normalizer = described_class.new(config)
57
- expect(normalizer.add_normalization("foo")).to eq("foo")
74
+ result = normalizer.add_normalization("foo")
75
+ expect(result).to eq("foo") # SqlLiteral is a String subclass, usable in Arel contexts
58
76
  end
59
77
  end
60
78
  end
79
+
80
+ describe "executed against PostgreSQL" do
81
+ with_model :Book do
82
+ table { |t| t.string :title }
83
+ end
84
+
85
+ it "strips accents and disallowed punctuation but preserves backslashes" do
86
+ Book.create!(title: "café's? déjà: vu\\path")
87
+ config = instance_double(PgSearch::Configuration, ignore: [:accents])
88
+ normalizer = described_class.new(config)
89
+ expression = normalizer.add_normalization(Book.arel_table[:title])
90
+
91
+ expect(Book.pick(expression)).to eq("cafes deja vu\\path")
92
+ end
93
+
94
+ it "preserves attributes when accents are not ignored" do
95
+ book = Book.create!(title: "café's? déjà: vu\\path")
96
+ config = instance_double(PgSearch::Configuration, ignore: [])
97
+ normalizer = described_class.new(config)
98
+ expression = normalizer.add_normalization(Book.arel_table[:title])
99
+
100
+ expect(Book.pick(expression)).to eq(book.title)
101
+ end
102
+
103
+ it "preserves quoted data rather than interpreting it as SQL" do
104
+ Book.create!
105
+ value = Arel::Nodes.build_quoted("café's? déjà: vu\\path")
106
+ config = instance_double(PgSearch::Configuration, ignore: [])
107
+ normalizer = described_class.new(config)
108
+
109
+ expect(normalizer.add_normalization(value)).to equal(value)
110
+ expect(Book.pick(normalizer.add_normalization(value)))
111
+ .to eq("café's? déjà: vu\\path")
112
+ end
113
+ end
61
114
  end
62
115
  # standard:enable RSpec/NestedGroups
@@ -2,17 +2,6 @@
2
2
 
3
3
  require "spec_helper"
4
4
 
5
- # For Active Record 5.x, the association reflection's cache needs be cleared
6
- # because we're stubbing the related constants.
7
- if ActiveRecord::VERSION::MAJOR == 5
8
- def clear_searchable_cache
9
- PgSearch::Document.reflect_on_association(:searchable).clear_association_scope_cache
10
- end
11
- else
12
- def clear_searchable_cache
13
- end
14
- end
15
-
16
5
  # standard:disable RSpec/NestedGroups
17
6
  describe PgSearch do
18
7
  describe ".multisearch" do
@@ -32,10 +21,7 @@ describe PgSearch do
32
21
  end
33
22
 
34
23
  context "with PgSearch.multisearch_options set to a Hash" do
35
- subject do
36
- clear_searchable_cache
37
- described_class.multisearch(query).map(&:searchable)
38
- end
24
+ subject { described_class.multisearch(query).map(&:searchable) }
39
25
 
40
26
  before { allow(described_class).to receive(:multisearch_options).and_return(using: :dmetaphone) }
41
27
 
@@ -57,10 +43,7 @@ describe PgSearch do
57
43
  end
58
44
 
59
45
  context "with PgSearch.multisearch_options set to a Proc" do
60
- subject do
61
- clear_searchable_cache
62
- described_class.multisearch(query, soundalike).map(&:searchable)
63
- end
46
+ subject { described_class.multisearch(query, soundalike).map(&:searchable) }
64
47
 
65
48
  before do
66
49
  allow(described_class).to receive(:multisearch_options) do
@@ -169,7 +152,6 @@ describe PgSearch do
169
152
 
170
153
  PgSearch::Multisearch.rebuild(SearchableSubclassModel)
171
154
 
172
- clear_searchable_cache
173
155
  expect(PgSearch::Document.count).to be 1
174
156
  expect(PgSearch::Document.first.searchable.class).to be SearchableSubclassModel
175
157
  expect(PgSearch::Document.first.searchable).to eq expected
@@ -184,7 +166,6 @@ describe PgSearch do
184
166
  PgSearch::Multisearch.rebuild(SearchableSubclassModel)
185
167
  expect(PgSearch::Document.count).to be 2
186
168
 
187
- clear_searchable_cache
188
169
  classes = PgSearch::Document.all.collect { |d| d.searchable.class }
189
170
  expect(classes).to include SearchableSubclassModel
190
171
  expect(classes).to include AnotherSearchableSubclassModel
metadata CHANGED
@@ -1,11 +1,10 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pg_search
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.3.8
4
+ version: 2.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Grant Hutchins
8
- - Case Commons, LLC
9
8
  bindir: bin
10
9
  cert_chain: []
11
10
  date: 1980-01-02 00:00:00.000000000 Z
@@ -16,33 +15,32 @@ dependencies:
16
15
  requirements:
17
16
  - - ">="
18
17
  - !ruby/object:Gem::Version
19
- version: '7.2'
18
+ version: '8.0'
20
19
  type: :runtime
21
20
  prerelease: false
22
21
  version_requirements: !ruby/object:Gem::Requirement
23
22
  requirements:
24
23
  - - ">="
25
24
  - !ruby/object:Gem::Version
26
- version: '7.2'
25
+ version: '8.0'
27
26
  - !ruby/object:Gem::Dependency
28
27
  name: activesupport
29
28
  requirement: !ruby/object:Gem::Requirement
30
29
  requirements:
31
30
  - - ">="
32
31
  - !ruby/object:Gem::Version
33
- version: '7.2'
32
+ version: '8.0'
34
33
  type: :runtime
35
34
  prerelease: false
36
35
  version_requirements: !ruby/object:Gem::Requirement
37
36
  requirements:
38
37
  - - ">="
39
38
  - !ruby/object:Gem::Version
40
- version: '7.2'
39
+ version: '8.0'
41
40
  description: PgSearch builds Active Record named scopes that take advantage of PostgreSQL's
42
41
  full text search
43
42
  email:
44
43
  - gems@nertzy.com
45
- - casecommons-dev@googlegroups.com
46
44
  executables: []
47
45
  extensions: []
48
46
  extra_rdoc_files: []
@@ -63,6 +61,8 @@ files:
63
61
  - LICENSE
64
62
  - README.md
65
63
  - Rakefile
64
+ - doc/plans/2026-09-05-arel-stack.md
65
+ - doc/plans/2026-09-06-arel-remaining-expressions.md
66
66
  - lib/pg_search.rb
67
67
  - lib/pg_search/configuration.rb
68
68
  - lib/pg_search/configuration/association.rb
@@ -129,7 +129,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
129
129
  - !ruby/object:Gem::Version
130
130
  version: '0'
131
131
  requirements: []
132
- rubygems_version: 4.0.18
132
+ rubygems_version: 4.0.20
133
133
  specification_version: 4
134
134
  summary: PgSearch builds Active Record named scopes that take advantage of PostgreSQL's
135
135
  full text search