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
|
@@ -8,8 +8,11 @@ describe PgSearch::Multisearchable do
|
|
|
8
8
|
|
|
9
9
|
describe "a model that is multisearchable" do
|
|
10
10
|
with_model :ModelThatIsMultisearchable do
|
|
11
|
+
table
|
|
12
|
+
|
|
11
13
|
model do
|
|
12
14
|
include PgSearch::Model
|
|
15
|
+
|
|
13
16
|
multisearchable
|
|
14
17
|
end
|
|
15
18
|
end
|
|
@@ -21,6 +24,7 @@ describe PgSearch::Multisearchable do
|
|
|
21
24
|
|
|
22
25
|
model do
|
|
23
26
|
include PgSearch::Model
|
|
27
|
+
|
|
24
28
|
multisearchable
|
|
25
29
|
|
|
26
30
|
has_many :multisearchable_children, dependent: :destroy
|
|
@@ -319,6 +323,7 @@ describe PgSearch::Multisearchable do
|
|
|
319
323
|
|
|
320
324
|
model do
|
|
321
325
|
include PgSearch::Model
|
|
326
|
+
|
|
322
327
|
multisearchable if: ->(record) { record.multisearchable? }
|
|
323
328
|
end
|
|
324
329
|
end
|
|
@@ -450,6 +455,7 @@ describe PgSearch::Multisearchable do
|
|
|
450
455
|
|
|
451
456
|
model do
|
|
452
457
|
include PgSearch::Model
|
|
458
|
+
|
|
453
459
|
multisearchable unless: ->(record) { record.not_multisearchable? }
|
|
454
460
|
end
|
|
455
461
|
end
|
|
@@ -585,6 +591,7 @@ describe PgSearch::Multisearchable do
|
|
|
585
591
|
|
|
586
592
|
model do
|
|
587
593
|
include PgSearch::Model
|
|
594
|
+
|
|
588
595
|
multisearchable if: :multisearchable?
|
|
589
596
|
end
|
|
590
597
|
end
|
|
@@ -723,6 +730,7 @@ describe PgSearch::Multisearchable do
|
|
|
723
730
|
|
|
724
731
|
model do
|
|
725
732
|
include PgSearch::Model
|
|
733
|
+
|
|
726
734
|
multisearchable unless: :not_multisearchable?
|
|
727
735
|
end
|
|
728
736
|
end
|
|
@@ -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 "
|
|
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
|
-
|
|
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 "
|
|
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
|
-
|
|
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 "
|
|
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
|
-
|
|
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 "
|
|
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
|
-
|
|
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
|
-
|
|
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
|
data/spec/lib/pg_search_spec.rb
CHANGED
|
@@ -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
|
|
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
|
|
|
@@ -45,6 +31,7 @@ describe PgSearch do
|
|
|
45
31
|
end
|
|
46
32
|
model do
|
|
47
33
|
include PgSearch::Model
|
|
34
|
+
|
|
48
35
|
multisearchable against: :title
|
|
49
36
|
end
|
|
50
37
|
end
|
|
@@ -56,10 +43,7 @@ describe PgSearch do
|
|
|
56
43
|
end
|
|
57
44
|
|
|
58
45
|
context "with PgSearch.multisearch_options set to a Proc" do
|
|
59
|
-
subject
|
|
60
|
-
clear_searchable_cache
|
|
61
|
-
described_class.multisearch(query, soundalike).map(&:searchable)
|
|
62
|
-
end
|
|
46
|
+
subject { described_class.multisearch(query, soundalike).map(&:searchable) }
|
|
63
47
|
|
|
64
48
|
before do
|
|
65
49
|
allow(described_class).to receive(:multisearch_options) do
|
|
@@ -79,6 +63,7 @@ describe PgSearch do
|
|
|
79
63
|
end
|
|
80
64
|
model do
|
|
81
65
|
include PgSearch::Model
|
|
66
|
+
|
|
82
67
|
multisearchable against: :title
|
|
83
68
|
end
|
|
84
69
|
end
|
|
@@ -111,6 +96,7 @@ describe PgSearch do
|
|
|
111
96
|
before do
|
|
112
97
|
searchable_subclass_model = Class.new(SuperclassModel) do
|
|
113
98
|
include PgSearch::Model
|
|
99
|
+
|
|
114
100
|
multisearchable against: :content
|
|
115
101
|
end
|
|
116
102
|
stub_const("SearchableSubclassModel", searchable_subclass_model)
|
|
@@ -166,7 +152,6 @@ describe PgSearch do
|
|
|
166
152
|
|
|
167
153
|
PgSearch::Multisearch.rebuild(SearchableSubclassModel)
|
|
168
154
|
|
|
169
|
-
clear_searchable_cache
|
|
170
155
|
expect(PgSearch::Document.count).to be 1
|
|
171
156
|
expect(PgSearch::Document.first.searchable.class).to be SearchableSubclassModel
|
|
172
157
|
expect(PgSearch::Document.first.searchable).to eq expected
|
|
@@ -181,7 +166,6 @@ describe PgSearch do
|
|
|
181
166
|
PgSearch::Multisearch.rebuild(SearchableSubclassModel)
|
|
182
167
|
expect(PgSearch::Document.count).to be 2
|
|
183
168
|
|
|
184
|
-
clear_searchable_cache
|
|
185
169
|
classes = PgSearch::Document.all.collect { |d| d.searchable.class }
|
|
186
170
|
expect(classes).to include SearchableSubclassModel
|
|
187
171
|
expect(classes).to include AnotherSearchableSubclassModel
|
|
@@ -203,6 +187,7 @@ describe PgSearch do
|
|
|
203
187
|
before do
|
|
204
188
|
searchable_subclass_model = Class.new(SuperclassModel) do
|
|
205
189
|
include PgSearch::Model
|
|
190
|
+
|
|
206
191
|
multisearchable against: :content
|
|
207
192
|
end
|
|
208
193
|
stub_const("SearchableSubclassModel", searchable_subclass_model)
|
data/spec/spec_helper.rb
CHANGED
|
@@ -1,18 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "warning"
|
|
4
|
-
|
|
5
|
-
# https://github.com/grodowski/undercover#setting-up-required-lcov-reporting
|
|
6
|
-
require "simplecov"
|
|
7
|
-
require "simplecov-lcov"
|
|
8
|
-
SimpleCov::Formatter::LcovFormatter.config.report_with_single_file = true
|
|
9
|
-
SimpleCov.formatter = SimpleCov::Formatter::LcovFormatter
|
|
10
|
-
SimpleCov.start do
|
|
11
|
-
add_filter(%r{^/spec/})
|
|
12
|
-
enable_coverage(:branch)
|
|
13
|
-
end
|
|
14
|
-
require "undercover"
|
|
15
|
-
|
|
16
3
|
require "bundler/setup"
|
|
17
4
|
require "pg_search"
|
|
18
5
|
|
metadata
CHANGED
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: pg_search
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.
|
|
4
|
+
version: 2.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Grant Hutchins
|
|
8
|
-
- Case Commons, LLC
|
|
9
|
-
autorequire:
|
|
10
8
|
bindir: bin
|
|
11
9
|
cert_chain: []
|
|
12
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
13
11
|
dependencies:
|
|
14
12
|
- !ruby/object:Gem::Dependency
|
|
15
13
|
name: activerecord
|
|
@@ -17,33 +15,32 @@ dependencies:
|
|
|
17
15
|
requirements:
|
|
18
16
|
- - ">="
|
|
19
17
|
- !ruby/object:Gem::Version
|
|
20
|
-
version: '
|
|
18
|
+
version: '8.0'
|
|
21
19
|
type: :runtime
|
|
22
20
|
prerelease: false
|
|
23
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
24
22
|
requirements:
|
|
25
23
|
- - ">="
|
|
26
24
|
- !ruby/object:Gem::Version
|
|
27
|
-
version: '
|
|
25
|
+
version: '8.0'
|
|
28
26
|
- !ruby/object:Gem::Dependency
|
|
29
27
|
name: activesupport
|
|
30
28
|
requirement: !ruby/object:Gem::Requirement
|
|
31
29
|
requirements:
|
|
32
30
|
- - ">="
|
|
33
31
|
- !ruby/object:Gem::Version
|
|
34
|
-
version: '
|
|
32
|
+
version: '8.0'
|
|
35
33
|
type: :runtime
|
|
36
34
|
prerelease: false
|
|
37
35
|
version_requirements: !ruby/object:Gem::Requirement
|
|
38
36
|
requirements:
|
|
39
37
|
- - ">="
|
|
40
38
|
- !ruby/object:Gem::Version
|
|
41
|
-
version: '
|
|
39
|
+
version: '8.0'
|
|
42
40
|
description: PgSearch builds Active Record named scopes that take advantage of PostgreSQL's
|
|
43
41
|
full text search
|
|
44
42
|
email:
|
|
45
43
|
- gems@nertzy.com
|
|
46
|
-
- casecommons-dev@googlegroups.com
|
|
47
44
|
executables: []
|
|
48
45
|
extensions: []
|
|
49
46
|
extra_rdoc_files: []
|
|
@@ -52,6 +49,7 @@ files:
|
|
|
52
49
|
- ".editorconfig"
|
|
53
50
|
- ".github/dependabot.yml"
|
|
54
51
|
- ".github/workflows/ci.yml"
|
|
52
|
+
- ".github/workflows/codeql.yml"
|
|
55
53
|
- ".gitignore"
|
|
56
54
|
- ".jrubyrc"
|
|
57
55
|
- ".rspec"
|
|
@@ -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
|
|
@@ -89,8 +89,6 @@ files:
|
|
|
89
89
|
- lib/pg_search/tasks.rb
|
|
90
90
|
- lib/pg_search/version.rb
|
|
91
91
|
- pg_search.gemspec
|
|
92
|
-
- spec/.rubocop.yml
|
|
93
|
-
- spec/integration/.rubocop.yml
|
|
94
92
|
- spec/integration/associations_spec.rb
|
|
95
93
|
- spec/integration/deprecation_spec.rb
|
|
96
94
|
- spec/integration/pagination_spec.rb
|
|
@@ -117,7 +115,6 @@ licenses:
|
|
|
117
115
|
- MIT
|
|
118
116
|
metadata:
|
|
119
117
|
rubygems_mfa_required: 'true'
|
|
120
|
-
post_install_message:
|
|
121
118
|
rdoc_options: []
|
|
122
119
|
require_paths:
|
|
123
120
|
- lib
|
|
@@ -125,15 +122,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
125
122
|
requirements:
|
|
126
123
|
- - ">="
|
|
127
124
|
- !ruby/object:Gem::Version
|
|
128
|
-
version: '3.
|
|
125
|
+
version: '3.3'
|
|
129
126
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
130
127
|
requirements:
|
|
131
128
|
- - ">="
|
|
132
129
|
- !ruby/object:Gem::Version
|
|
133
130
|
version: '0'
|
|
134
131
|
requirements: []
|
|
135
|
-
rubygems_version:
|
|
136
|
-
signing_key:
|
|
132
|
+
rubygems_version: 4.0.20
|
|
137
133
|
specification_version: 4
|
|
138
134
|
summary: PgSearch builds Active Record named scopes that take advantage of PostgreSQL's
|
|
139
135
|
full text search
|
data/spec/.rubocop.yml
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
inherit_from:
|
|
2
|
-
- ../.rubocop.yml
|
|
3
|
-
|
|
4
|
-
RSpec/ContextWording:
|
|
5
|
-
Prefixes:
|
|
6
|
-
- using
|
|
7
|
-
- via
|
|
8
|
-
- when
|
|
9
|
-
- with
|
|
10
|
-
- without
|
|
11
|
-
|
|
12
|
-
RSpec/DescribedClass:
|
|
13
|
-
Enabled: true
|
|
14
|
-
|
|
15
|
-
RSpec/ExampleLength:
|
|
16
|
-
Max: 15
|
|
17
|
-
|
|
18
|
-
RSpec/ExpectInHook:
|
|
19
|
-
Enabled: false
|
|
20
|
-
|
|
21
|
-
RSpec/FilePath:
|
|
22
|
-
CustomTransform:
|
|
23
|
-
TSearch: "tsearch"
|
|
24
|
-
DMetaphone: "dmetaphone"
|
|
25
|
-
|
|
26
|
-
RSpec/MultipleExpectations:
|
|
27
|
-
Max: 5
|