ransack 4.3.0 → 4.4.1
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/README.md +6 -4
- data/lib/polyamorous/polyamorous.rb +1 -1
- data/lib/ransack/adapters/active_record/context.rb +30 -3
- data/lib/ransack/context.rb +3 -0
- data/lib/ransack/helpers/form_builder.rb +6 -7
- data/lib/ransack/helpers/form_helper.rb +86 -20
- data/lib/ransack/locale/ja.yml +51 -51
- data/lib/ransack/locale/ko.yml +6 -6
- data/lib/ransack/locale/uk.yml +72 -0
- data/lib/ransack/nodes/condition.rb +35 -5
- data/lib/ransack/nodes/grouping.rb +1 -1
- data/lib/ransack/nodes/sort.rb +1 -1
- data/lib/ransack/nodes/value.rb +1 -1
- data/lib/ransack/search.rb +1 -1
- data/lib/ransack/version.rb +1 -1
- data/lib/ransack.rb +4 -0
- data/spec/console.rb +3 -15
- data/spec/factories/articles.rb +7 -0
- data/spec/factories/comments.rb +7 -0
- data/spec/factories/notes.rb +13 -0
- data/spec/factories/people.rb +10 -0
- data/spec/factories/tags.rb +5 -0
- data/spec/polyamorous/join_association_spec.rb +0 -1
- data/spec/polyamorous/join_dependency_spec.rb +0 -1
- data/spec/ransack/adapters/active_record/base_spec.rb +139 -2
- data/spec/ransack/adapters/active_record/context_spec.rb +72 -0
- data/spec/ransack/helpers/form_builder_spec.rb +0 -2
- data/spec/ransack/helpers/form_helper_spec.rb +219 -5
- data/spec/ransack/invalid_search_error_spec.rb +27 -0
- data/spec/ransack/nodes/condition_spec.rb +229 -0
- data/spec/ransack/nodes/grouping_spec.rb +2 -2
- data/spec/ransack/nodes/value_spec.rb +12 -1
- data/spec/ransack/predicate_spec.rb +0 -1
- data/spec/ransack/ransacker_spec.rb +69 -0
- data/spec/ransack/search_spec.rb +115 -2
- data/spec/ransack/translate_spec.rb +0 -1
- data/spec/spec_helper.rb +7 -21
- data/spec/support/schema.rb +36 -9
- metadata +51 -93
- data/.github/FUNDING.yml +0 -3
- data/.github/SECURITY.md +0 -12
- data/.github/workflows/codeql.yml +0 -72
- data/.github/workflows/cronjob.yml +0 -141
- data/.github/workflows/deploy.yml +0 -35
- data/.github/workflows/rubocop.yml +0 -20
- data/.github/workflows/test-deploy.yml +0 -29
- data/.github/workflows/test.yml +0 -183
- data/.gitignore +0 -7
- data/.nojekyll +0 -0
- data/.rubocop.yml +0 -50
- data/CHANGELOG.md +0 -1193
- data/CONTRIBUTING.md +0 -171
- data/Gemfile +0 -58
- data/Rakefile +0 -24
- data/bug_report_templates/test-ransack-scope-and-column-same-name.rb +0 -78
- data/bug_report_templates/test-ransacker-arel-present-predicate.rb +0 -75
- data/docs/.gitignore +0 -19
- data/docs/.nojekyll +0 -0
- data/docs/babel.config.js +0 -3
- data/docs/blog/2022-03-27-ransack-3.0.0.md +0 -20
- data/docs/docs/getting-started/_category_.json +0 -4
- data/docs/docs/getting-started/advanced-mode.md +0 -46
- data/docs/docs/getting-started/configuration.md +0 -47
- data/docs/docs/getting-started/search-matches.md +0 -67
- data/docs/docs/getting-started/simple-mode.md +0 -289
- data/docs/docs/getting-started/sorting.md +0 -71
- data/docs/docs/getting-started/using-predicates.md +0 -282
- data/docs/docs/going-further/_category_.json +0 -4
- data/docs/docs/going-further/acts-as-taggable-on.md +0 -114
- data/docs/docs/going-further/associations.md +0 -70
- data/docs/docs/going-further/custom-predicates.md +0 -52
- data/docs/docs/going-further/documentation.md +0 -43
- data/docs/docs/going-further/exporting-to-csv.md +0 -49
- data/docs/docs/going-further/external-guides.md +0 -57
- data/docs/docs/going-further/form-customisation.md +0 -63
- data/docs/docs/going-further/i18n.md +0 -53
- data/docs/docs/going-further/img/create_release.png +0 -0
- data/docs/docs/going-further/merging-searches.md +0 -41
- data/docs/docs/going-further/other-notes.md +0 -425
- data/docs/docs/going-further/polymorphic-search.md +0 -46
- data/docs/docs/going-further/ransackers.md +0 -331
- data/docs/docs/going-further/release_process.md +0 -36
- data/docs/docs/going-further/saving-queries.md +0 -82
- data/docs/docs/going-further/searching-postgres.md +0 -57
- data/docs/docs/going-further/wiki-contributors.md +0 -82
- data/docs/docs/intro.md +0 -99
- data/docs/docusaurus.config.js +0 -120
- data/docs/package.json +0 -42
- data/docs/sidebars.js +0 -31
- data/docs/src/components/HomepageFeatures/index.js +0 -64
- data/docs/src/components/HomepageFeatures/styles.module.css +0 -11
- data/docs/src/css/custom.css +0 -39
- data/docs/src/pages/index.module.css +0 -23
- data/docs/src/pages/markdown-page.md +0 -7
- data/docs/static/.nojekyll +0 -0
- data/docs/static/img/docusaurus.png +0 -0
- data/docs/static/img/favicon.ico +0 -0
- data/docs/static/img/logo.svg +0 -1
- data/docs/static/img/tutorial/docsVersionDropdown.png +0 -0
- data/docs/static/img/tutorial/localeDropdown.png +0 -0
- data/docs/static/img/undraw_docusaurus_mountain.svg +0 -171
- data/docs/static/img/undraw_docusaurus_react.svg +0 -170
- data/docs/static/img/undraw_docusaurus_tree.svg +0 -40
- data/docs/static/logo/ransack-h.png +0 -0
- data/docs/static/logo/ransack-h.svg +0 -34
- data/docs/static/logo/ransack-v.png +0 -0
- data/docs/static/logo/ransack-v.svg +0 -34
- data/docs/static/logo/ransack.png +0 -0
- data/docs/static/logo/ransack.svg +0 -21
- data/docs/yarn.lock +0 -8884
- data/ransack.gemspec +0 -26
- data/spec/blueprints/articles.rb +0 -5
- data/spec/blueprints/comments.rb +0 -5
- data/spec/blueprints/notes.rb +0 -5
- data/spec/blueprints/people.rb +0 -8
- data/spec/blueprints/tags.rb +0 -3
@@ -0,0 +1,10 @@
|
|
1
|
+
FactoryBot.define do
|
2
|
+
factory :person do
|
3
|
+
name { Faker::Name.name }
|
4
|
+
email { "test@example.com" }
|
5
|
+
sequence(:salary) { |n| 30000 + (n * 1000) }
|
6
|
+
only_sort { Faker::Lorem.words(number: 3).join(' ') }
|
7
|
+
only_search { Faker::Lorem.words(number: 3).join(' ') }
|
8
|
+
only_admin { Faker::Lorem.words(number: 3).join(' ') }
|
9
|
+
end
|
10
|
+
end
|
@@ -4,7 +4,6 @@ module Ransack
|
|
4
4
|
module Adapters
|
5
5
|
module ActiveRecord
|
6
6
|
describe Base do
|
7
|
-
|
8
7
|
subject { ::ActiveRecord::Base }
|
9
8
|
|
10
9
|
it { should respond_to :ransack }
|
@@ -124,7 +123,6 @@ module Ransack
|
|
124
123
|
expect(s.result.to_sql).to (include rails7_and_mysql ? %q{age > '0'} : 'age > 0')
|
125
124
|
end
|
126
125
|
end
|
127
|
-
|
128
126
|
end
|
129
127
|
|
130
128
|
it 'does not raise exception for string :params argument' do
|
@@ -195,6 +193,41 @@ module Ransack
|
|
195
193
|
end
|
196
194
|
end
|
197
195
|
|
196
|
+
context 'negative conditions on related object with HABTM associations' do
|
197
|
+
let(:medieval) { Tag.create!(name: 'Medieval') }
|
198
|
+
let(:fantasy) { Tag.create!(name: 'Fantasy') }
|
199
|
+
let(:arthur) { Article.create!(title: 'King Arthur') }
|
200
|
+
let(:marco) { Article.create!(title: 'Marco Polo') }
|
201
|
+
let(:comment_arthur) { marco.comments.create!(body: 'King Arthur comment') }
|
202
|
+
let(:comment_marco) { arthur.comments.create!(body: 'Marco Polo comment') }
|
203
|
+
|
204
|
+
before do
|
205
|
+
comment_arthur.tags << medieval
|
206
|
+
comment_marco.tags << fantasy
|
207
|
+
end
|
208
|
+
|
209
|
+
it 'removes redundant joins from top query' do
|
210
|
+
s = Article.ransack(comments_tags_name_not_eq: "Fantasy")
|
211
|
+
sql = s.result.to_sql
|
212
|
+
expect(sql).to include('LEFT OUTER JOIN')
|
213
|
+
end
|
214
|
+
|
215
|
+
it 'handles != for single values' do
|
216
|
+
s = Article.ransack(comments_tags_name_not_eq: "Fantasy")
|
217
|
+
articles = s.result.to_a
|
218
|
+
expect(articles).to include marco
|
219
|
+
expect(articles).to_not include arthur
|
220
|
+
end
|
221
|
+
|
222
|
+
it 'handles NOT IN for multiple attributes' do
|
223
|
+
s = Article.ransack(comments_tags_name_not_in: ["Fantasy", "Scifi"])
|
224
|
+
articles = s.result.to_a
|
225
|
+
|
226
|
+
expect(articles).to include marco
|
227
|
+
expect(articles).to_not include arthur
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
198
231
|
context 'negative conditions on self-referenced associations' do
|
199
232
|
let(:pop) { Person.create!(name: 'Grandpa') }
|
200
233
|
let(:dad) { Person.create!(name: 'Father') }
|
@@ -378,6 +411,63 @@ module Ransack
|
|
378
411
|
expect(s.result.to_a).to eq [p]
|
379
412
|
end
|
380
413
|
|
414
|
+
if ::ActiveRecord::VERSION::MAJOR >= 7 && ActiveRecord::Base.respond_to?(:normalizes)
|
415
|
+
context 'with ActiveRecord::normalizes' do
|
416
|
+
around(:each) do |example|
|
417
|
+
# Create a temporary model class with normalization for testing
|
418
|
+
test_class = Class.new(ActiveRecord::Base) do
|
419
|
+
self.table_name = 'people'
|
420
|
+
normalizes :name, with: ->(name) { name.gsub(/[^a-z0-9]/, '_') }
|
421
|
+
|
422
|
+
def self.ransackable_attributes(auth_object = nil)
|
423
|
+
Person.ransackable_attributes(auth_object)
|
424
|
+
end
|
425
|
+
|
426
|
+
def self.name
|
427
|
+
'TestPersonWithNormalization'
|
428
|
+
end
|
429
|
+
end
|
430
|
+
|
431
|
+
stub_const('TestPersonWithNormalization', test_class)
|
432
|
+
example.run
|
433
|
+
end
|
434
|
+
|
435
|
+
it 'should not apply normalization to LIKE wildcards for cont predicate' do
|
436
|
+
# Create a person with characters that would be normalized
|
437
|
+
p = TestPersonWithNormalization.create!(name: 'foo%bar')
|
438
|
+
expect(p.reload.name).to eq('foo_bar') # Verify normalization happened on storage
|
439
|
+
|
440
|
+
# Search should find the person using the original search term
|
441
|
+
s = TestPersonWithNormalization.ransack(name_cont: 'foo')
|
442
|
+
expect(s.result.to_a).to eq [p]
|
443
|
+
|
444
|
+
# Verify the SQL contains proper LIKE wildcards, not normalized ones
|
445
|
+
sql = s.result.to_sql
|
446
|
+
expect(sql).to include("LIKE '%foo%'")
|
447
|
+
expect(sql).not_to include("LIKE '_foo_'")
|
448
|
+
end
|
449
|
+
|
450
|
+
it 'should not apply normalization to LIKE wildcards for other LIKE predicates' do
|
451
|
+
p = TestPersonWithNormalization.create!(name: 'foo%bar')
|
452
|
+
|
453
|
+
# Test start predicate
|
454
|
+
s = TestPersonWithNormalization.ransack(name_start: 'foo')
|
455
|
+
expect(s.result.to_a).to eq [p]
|
456
|
+
expect(s.result.to_sql).to include("LIKE 'foo%'")
|
457
|
+
|
458
|
+
# Test end predicate
|
459
|
+
s = TestPersonWithNormalization.ransack(name_end: 'bar')
|
460
|
+
expect(s.result.to_a).to eq [p]
|
461
|
+
expect(s.result.to_sql).to include("LIKE '%bar'")
|
462
|
+
|
463
|
+
# Test i_cont predicate
|
464
|
+
s = TestPersonWithNormalization.ransack(name_i_cont: 'FOO')
|
465
|
+
expect(s.result.to_a).to eq [p]
|
466
|
+
expect(s.result.to_sql).to include("LIKE '%foo%'")
|
467
|
+
end
|
468
|
+
end
|
469
|
+
end
|
470
|
+
|
381
471
|
context 'searching by underscores' do
|
382
472
|
# when escaping is supported right in LIKE expression without adding extra expressions
|
383
473
|
def self.simple_escaping?
|
@@ -412,6 +502,15 @@ module Ransack
|
|
412
502
|
expect(s.result.map(&:id)).to eq [3, 2, 1]
|
413
503
|
end
|
414
504
|
|
505
|
+
it 'should function correctly with HABTM associations' do
|
506
|
+
article = Article.first
|
507
|
+
tag = article.tags.first
|
508
|
+
s = Person.ransack(article_tags_in: [tag.id])
|
509
|
+
|
510
|
+
expect(s.result.count).to be 1
|
511
|
+
expect(s.result.map(&:id)).to eq [article.person.id]
|
512
|
+
end
|
513
|
+
|
415
514
|
it 'should function correctly when passing an array of strings' do
|
416
515
|
a, b = Person.select(:id).order(:id).limit(2).map { |a| a.id.to_s }
|
417
516
|
|
@@ -630,6 +729,44 @@ module Ransack
|
|
630
729
|
end
|
631
730
|
end
|
632
731
|
|
732
|
+
context 'ransacker with different types' do
|
733
|
+
it 'handles string type ransacker correctly' do
|
734
|
+
s = Person.ransack(name_case_insensitive_eq: 'test')
|
735
|
+
expect(s.result.to_sql).to match(/LOWER\(.*\) = 'test'/)
|
736
|
+
end
|
737
|
+
|
738
|
+
it 'handles integer type ransacker correctly' do
|
739
|
+
s = Person.ransack(sql_literal_id_eq: 1)
|
740
|
+
expect(s.result.to_sql).to match(/people\.id = 1/)
|
741
|
+
end
|
742
|
+
end
|
743
|
+
|
744
|
+
context 'ransacker with formatter returning nil' do
|
745
|
+
it 'handles formatter returning nil gracefully' do
|
746
|
+
# This tests the edge case where a formatter might return nil
|
747
|
+
s = Person.ransack(article_tags_eq: 999999) # Non-existent tag ID
|
748
|
+
expect { s.result.to_sql }.not_to raise_error
|
749
|
+
end
|
750
|
+
end
|
751
|
+
|
752
|
+
context 'ransacker with array formatters' do
|
753
|
+
it 'handles array_people_ids formatter correctly' do
|
754
|
+
person1 = Person.create!(name: 'Test1')
|
755
|
+
person2 = Person.create!(name: 'Test2')
|
756
|
+
|
757
|
+
s = Person.ransack(array_people_ids_eq: 'test')
|
758
|
+
expect { s.result }.not_to raise_error
|
759
|
+
end
|
760
|
+
|
761
|
+
it 'handles array_where_people_ids formatter correctly' do
|
762
|
+
person1 = Person.create!(name: 'Test1')
|
763
|
+
person2 = Person.create!(name: 'Test2')
|
764
|
+
|
765
|
+
s = Person.ransack(array_where_people_ids_eq: [person1.id, person2.id])
|
766
|
+
expect { s.result }.not_to raise_error
|
767
|
+
end
|
768
|
+
end
|
769
|
+
|
633
770
|
context 'regular sorting' do
|
634
771
|
it 'allows sort by desc' do
|
635
772
|
search = Person.ransack(sorts: ['name desc'])
|
@@ -97,6 +97,62 @@ module Ransack
|
|
97
97
|
|
98
98
|
expect(search.result.to_sql).to match /.comments.\..person_id. = .people.\..id./
|
99
99
|
end
|
100
|
+
|
101
|
+
it 'handles Arel::Nodes::And with children' do
|
102
|
+
# Create a mock Arel::Nodes::And with children for testing
|
103
|
+
search = Search.new(Person, { articles_title_not_eq: 'some_title', articles_body_not_eq: 'some_body' }, context: subject)
|
104
|
+
attribute = search.conditions.first.attributes.first
|
105
|
+
constraints = subject.build_correlated_subquery(attribute.parent).constraints
|
106
|
+
constraint = constraints.first
|
107
|
+
|
108
|
+
expect(constraints.length).to eql 1
|
109
|
+
expect(constraint.left.name).to eql 'person_id'
|
110
|
+
expect(constraint.left.relation.name).to eql 'articles'
|
111
|
+
expect(constraint.right.name).to eql 'id'
|
112
|
+
expect(constraint.right.relation.name).to eql 'people'
|
113
|
+
end
|
114
|
+
|
115
|
+
it 'correctly extracts correlated key from complex AND conditions' do
|
116
|
+
# Test with multiple nested conditions to ensure the children traversal works
|
117
|
+
search = Search.new(
|
118
|
+
Person,
|
119
|
+
{
|
120
|
+
articles_title_not_eq: 'title',
|
121
|
+
articles_body_not_eq: 'body',
|
122
|
+
articles_published_eq: true
|
123
|
+
},
|
124
|
+
context: subject
|
125
|
+
)
|
126
|
+
|
127
|
+
attribute = search.conditions.first.attributes.first
|
128
|
+
constraints = subject.build_correlated_subquery(attribute.parent).constraints
|
129
|
+
constraint = constraints.first
|
130
|
+
|
131
|
+
expect(constraints.length).to eql 1
|
132
|
+
expect(constraint.left.relation.name).to eql 'articles'
|
133
|
+
expect(constraint.left.name).to eql 'person_id'
|
134
|
+
expect(constraint.right.relation.name).to eql 'people'
|
135
|
+
expect(constraint.right.name).to eql 'id'
|
136
|
+
end
|
137
|
+
|
138
|
+
it 'build correlated subquery for polymorphic & default_scope when predicate is not_cont_all' do
|
139
|
+
search = Search.new(Article,
|
140
|
+
g: [
|
141
|
+
{
|
142
|
+
m: "and",
|
143
|
+
c: [
|
144
|
+
{
|
145
|
+
a: ["recent_notes_note"],
|
146
|
+
p: "not_eq",
|
147
|
+
v: ["some_note"],
|
148
|
+
}
|
149
|
+
]
|
150
|
+
}
|
151
|
+
],
|
152
|
+
)
|
153
|
+
|
154
|
+
expect(search.result.to_sql).to match /(.notes.\..note. != \'some_note\')/
|
155
|
+
end
|
100
156
|
end
|
101
157
|
|
102
158
|
describe 'sharing context across searches' do
|
@@ -141,6 +197,22 @@ module Ransack
|
|
141
197
|
expect(attribute.relation.table_alias).to be_nil
|
142
198
|
end
|
143
199
|
|
200
|
+
describe '#type_for' do
|
201
|
+
it 'returns nil when column does not exist instead of raising NoMethodError' do
|
202
|
+
# Create a mock attribute that references a non-existent column
|
203
|
+
mock_attr = double('attribute')
|
204
|
+
allow(mock_attr).to receive(:valid?).and_return(true)
|
205
|
+
|
206
|
+
mock_arel_attr = double('arel_attribute')
|
207
|
+
allow(mock_arel_attr).to receive(:relation).and_return(Person.arel_table)
|
208
|
+
allow(mock_arel_attr).to receive(:name).and_return('nonexistent_column')
|
209
|
+
allow(mock_attr).to receive(:arel_attribute).and_return(mock_arel_attr)
|
210
|
+
allow(mock_attr).to receive(:klass).and_return(Person)
|
211
|
+
|
212
|
+
# This should return nil instead of raising an error
|
213
|
+
expect(subject.type_for(mock_attr)).to be_nil
|
214
|
+
end
|
215
|
+
end
|
144
216
|
end
|
145
217
|
end
|
146
218
|
end
|
@@ -3,7 +3,6 @@ require 'spec_helper'
|
|
3
3
|
module Ransack
|
4
4
|
module Helpers
|
5
5
|
describe FormBuilder do
|
6
|
-
|
7
6
|
router = ActionDispatch::Routing::RouteSet.new
|
8
7
|
router.draw do
|
9
8
|
resources :people, :comments, :notes
|
@@ -165,7 +164,6 @@ module Ransack
|
|
165
164
|
def date_select_html(val)
|
166
165
|
%(<option value="#{val}" selected="selected">#{val}</option>)
|
167
166
|
end
|
168
|
-
|
169
167
|
end
|
170
168
|
end
|
171
169
|
end
|
@@ -3,7 +3,6 @@ require 'spec_helper'
|
|
3
3
|
module Ransack
|
4
4
|
module Helpers
|
5
5
|
describe FormHelper do
|
6
|
-
|
7
6
|
router = ActionDispatch::Routing::RouteSet.new
|
8
7
|
router.draw do
|
9
8
|
resources :people, :notes
|
@@ -458,9 +457,7 @@ module Ransack
|
|
458
457
|
end
|
459
458
|
|
460
459
|
context 'view has existing parameters' do
|
461
|
-
|
462
460
|
describe '#sort_link should not remove existing params' do
|
463
|
-
|
464
461
|
before { @controller.view_context.params[:exist] = 'existing' }
|
465
462
|
|
466
463
|
subject {
|
@@ -478,7 +475,6 @@ module Ransack
|
|
478
475
|
end
|
479
476
|
|
480
477
|
describe '#sort_url should not remove existing params' do
|
481
|
-
|
482
478
|
before { @controller.view_context.params[:exist] = 'existing' }
|
483
479
|
|
484
480
|
subject {
|
@@ -496,12 +492,12 @@ module Ransack
|
|
496
492
|
end
|
497
493
|
|
498
494
|
context 'using a real ActionController::Parameter object' do
|
499
|
-
|
500
495
|
describe 'with symbol q:, #sort_link should include search params' do
|
501
496
|
subject { @controller.view_context.sort_link(Person.ransack, :name) }
|
502
497
|
let(:params) { ActionController::Parameters.new(
|
503
498
|
{ q: { name_eq: 'TEST' }, controller: 'people' }
|
504
499
|
) }
|
500
|
+
|
505
501
|
before { @controller.instance_variable_set(:@params, params) }
|
506
502
|
|
507
503
|
it {
|
@@ -517,6 +513,7 @@ module Ransack
|
|
517
513
|
let(:params) { ActionController::Parameters.new(
|
518
514
|
{ q: { name_eq: 'TEST' }, controller: 'people' }
|
519
515
|
) }
|
516
|
+
|
520
517
|
before { @controller.instance_variable_set(:@params, params) }
|
521
518
|
|
522
519
|
it {
|
@@ -533,6 +530,7 @@ module Ransack
|
|
533
530
|
ActionController::Parameters.new(
|
534
531
|
{ 'q' => { name_eq: 'Test2' }, controller: 'people' }
|
535
532
|
) }
|
533
|
+
|
536
534
|
before { @controller.instance_variable_set(:@params, params) }
|
537
535
|
|
538
536
|
it {
|
@@ -549,6 +547,7 @@ module Ransack
|
|
549
547
|
ActionController::Parameters.new(
|
550
548
|
{ 'q' => { name_eq: 'Test2' }, controller: 'people' }
|
551
549
|
) }
|
550
|
+
|
552
551
|
before { @controller.instance_variable_set(:@params, params) }
|
553
552
|
|
554
553
|
it {
|
@@ -850,12 +849,227 @@ module Ransack
|
|
850
849
|
before do
|
851
850
|
Ransack.configure { |c| c.search_key = :example }
|
852
851
|
end
|
852
|
+
after do
|
853
|
+
Ransack.configure { |c| c.search_key = :q }
|
854
|
+
end
|
853
855
|
subject {
|
854
856
|
@controller.view_context
|
855
857
|
.search_form_for(Person.ransack) { |f| f.text_field :name_eq }
|
856
858
|
}
|
857
859
|
it { should match /example_name_eq/ }
|
858
860
|
end
|
861
|
+
|
862
|
+
describe '#search_form_with with default format' do
|
863
|
+
subject { @controller.view_context
|
864
|
+
.search_form_with(model: Person.ransack) {} }
|
865
|
+
it { should match /action="\/people"/ }
|
866
|
+
end
|
867
|
+
|
868
|
+
describe '#search_form_with with pdf format' do
|
869
|
+
subject {
|
870
|
+
@controller.view_context
|
871
|
+
.search_form_with(model: Person.ransack, format: :pdf) {}
|
872
|
+
}
|
873
|
+
it { should match /action="\/people.pdf"/ }
|
874
|
+
end
|
875
|
+
|
876
|
+
describe '#search_form_with with json format' do
|
877
|
+
subject {
|
878
|
+
@controller.view_context
|
879
|
+
.search_form_with(model: Person.ransack, format: :json) {}
|
880
|
+
}
|
881
|
+
it { should match /action="\/people.json"/ }
|
882
|
+
end
|
883
|
+
|
884
|
+
describe '#search_form_with with an array of routes' do
|
885
|
+
subject {
|
886
|
+
@controller.view_context
|
887
|
+
.search_form_with(model: [:admin, Comment.ransack]) {}
|
888
|
+
}
|
889
|
+
it { should match /action="\/admin\/comments"/ }
|
890
|
+
end
|
891
|
+
|
892
|
+
describe '#search_form_with with custom default search key' do
|
893
|
+
before do
|
894
|
+
Ransack.configure { |c| c.search_key = :example }
|
895
|
+
end
|
896
|
+
after do
|
897
|
+
Ransack.configure { |c| c.search_key = :q }
|
898
|
+
end
|
899
|
+
subject {
|
900
|
+
@controller.view_context
|
901
|
+
.search_form_with(model: Person.ransack) { |f| f.text_field :name_eq }
|
902
|
+
}
|
903
|
+
it { should match /example\[name_eq\]/ }
|
904
|
+
end
|
905
|
+
|
906
|
+
describe '#search_form_with without Ransack::Search object' do
|
907
|
+
it 'raises ArgumentError' do
|
908
|
+
expect {
|
909
|
+
@controller.view_context.search_form_with(model: "not a search object") {}
|
910
|
+
}.to raise_error(ArgumentError, 'No Ransack::Search object was provided to search_form_with!')
|
911
|
+
end
|
912
|
+
end
|
913
|
+
|
914
|
+
describe '#turbo_search_form_for with default options' do
|
915
|
+
subject {
|
916
|
+
@controller.view_context
|
917
|
+
.turbo_search_form_for(Person.ransack) {}
|
918
|
+
}
|
919
|
+
it { should match /action="\/people"/ }
|
920
|
+
it { should match /method="post"/ }
|
921
|
+
it { should match /data-turbo-action="advance"/ }
|
922
|
+
end
|
923
|
+
|
924
|
+
describe '#turbo_search_form_for with custom method' do
|
925
|
+
subject {
|
926
|
+
@controller.view_context
|
927
|
+
.turbo_search_form_for(Person.ransack, method: :patch) {}
|
928
|
+
}
|
929
|
+
it { should match /method="post"/ }
|
930
|
+
it { should match /name="_method" value="patch"/ }
|
931
|
+
it { should match /data-turbo-action="advance"/ }
|
932
|
+
end
|
933
|
+
|
934
|
+
describe '#turbo_search_form_for with turbo_frame' do
|
935
|
+
subject {
|
936
|
+
@controller.view_context
|
937
|
+
.turbo_search_form_for(Person.ransack, turbo_frame: 'search_results') {}
|
938
|
+
}
|
939
|
+
it { should match /data-turbo-frame="search_results"/ }
|
940
|
+
end
|
941
|
+
|
942
|
+
describe '#turbo_search_form_for with custom turbo_action' do
|
943
|
+
subject {
|
944
|
+
@controller.view_context
|
945
|
+
.turbo_search_form_for(Person.ransack, turbo_action: 'replace') {}
|
946
|
+
}
|
947
|
+
it { should match /data-turbo-action="replace"/ }
|
948
|
+
end
|
949
|
+
|
950
|
+
describe '#turbo_search_form_for with format' do
|
951
|
+
subject {
|
952
|
+
@controller.view_context
|
953
|
+
.turbo_search_form_for(Person.ransack, format: :json) {}
|
954
|
+
}
|
955
|
+
it { should match /action="\/people.json"/ }
|
956
|
+
end
|
957
|
+
|
958
|
+
describe '#turbo_search_form_for with array of routes' do
|
959
|
+
subject {
|
960
|
+
@controller.view_context
|
961
|
+
.turbo_search_form_for([:admin, Comment.ransack]) {}
|
962
|
+
}
|
963
|
+
it { should match /action="\/admin\/comments"/ }
|
964
|
+
end
|
965
|
+
|
966
|
+
describe '#turbo_search_form_for with custom search key' do
|
967
|
+
before do
|
968
|
+
Ransack.configure { |c| c.search_key = :example }
|
969
|
+
end
|
970
|
+
after do
|
971
|
+
Ransack.configure { |c| c.search_key = :q }
|
972
|
+
end
|
973
|
+
subject {
|
974
|
+
@controller.view_context
|
975
|
+
.turbo_search_form_for(Person.ransack) { |f| f.text_field :name_eq }
|
976
|
+
}
|
977
|
+
it { should match /example_name_eq/ }
|
978
|
+
end
|
979
|
+
|
980
|
+
describe '#turbo_search_form_for without Ransack::Search object' do
|
981
|
+
it 'raises ArgumentError' do
|
982
|
+
expect {
|
983
|
+
@controller.view_context.turbo_search_form_for("not a search object") {}
|
984
|
+
}.to raise_error(ArgumentError, 'No Ransack::Search object was provided to turbo_search_form_for!')
|
985
|
+
end
|
986
|
+
end
|
987
|
+
|
988
|
+
describe 'private helper methods' do
|
989
|
+
let(:helper) { @controller.view_context }
|
990
|
+
let(:search) { Person.ransack }
|
991
|
+
|
992
|
+
describe '#build_turbo_options' do
|
993
|
+
it 'builds turbo options with frame' do
|
994
|
+
options = { turbo_frame: 'results', turbo_action: 'replace' }
|
995
|
+
result = helper.send(:build_turbo_options, options)
|
996
|
+
expect(result).to eq({
|
997
|
+
data: {
|
998
|
+
turbo_frame: 'results',
|
999
|
+
turbo_action: 'replace'
|
1000
|
+
}
|
1001
|
+
})
|
1002
|
+
expect(options).to be_empty
|
1003
|
+
end
|
1004
|
+
|
1005
|
+
it 'builds turbo options without frame' do
|
1006
|
+
options = { turbo_action: 'advance' }
|
1007
|
+
result = helper.send(:build_turbo_options, options)
|
1008
|
+
expect(result).to eq({ data: { turbo_action: 'advance' } })
|
1009
|
+
end
|
1010
|
+
|
1011
|
+
it 'uses default turbo action' do
|
1012
|
+
options = {}
|
1013
|
+
result = helper.send(:build_turbo_options, options)
|
1014
|
+
expect(result).to eq({ data: { turbo_action: 'advance' } })
|
1015
|
+
end
|
1016
|
+
end
|
1017
|
+
|
1018
|
+
describe '#build_html_options' do
|
1019
|
+
it 'builds HTML options with correct method' do
|
1020
|
+
options = { class: 'custom' }
|
1021
|
+
result = helper.send(:build_html_options, search, options, :post)
|
1022
|
+
expect(result[:method]).to eq(:post)
|
1023
|
+
expect(result[:class]).to include('custom')
|
1024
|
+
end
|
1025
|
+
end
|
1026
|
+
|
1027
|
+
describe '#extract_search_and_set_url' do
|
1028
|
+
it 'extracts search from Ransack::Search object' do
|
1029
|
+
options = {}
|
1030
|
+
result = helper.send(:extract_search_and_set_url, search, options, 'search_form_for')
|
1031
|
+
expect(result).to eq(search)
|
1032
|
+
expect(options[:url]).to match(/people/)
|
1033
|
+
end
|
1034
|
+
|
1035
|
+
it 'extracts search from array with Search object' do
|
1036
|
+
options = {}
|
1037
|
+
comment_search = Comment.ransack
|
1038
|
+
result = helper.send(:extract_search_and_set_url, [:admin, comment_search], options, 'search_form_for')
|
1039
|
+
expect(result).to eq(comment_search)
|
1040
|
+
expect(options[:url]).to match(/admin/)
|
1041
|
+
end
|
1042
|
+
|
1043
|
+
it 'raises error for invalid record with correct method name' do
|
1044
|
+
options = {}
|
1045
|
+
expect {
|
1046
|
+
helper.send(:extract_search_and_set_url, "invalid", options, 'turbo_search_form_for')
|
1047
|
+
}.to raise_error(ArgumentError, 'No Ransack::Search object was provided to turbo_search_form_for!')
|
1048
|
+
end
|
1049
|
+
|
1050
|
+
it 'extracts search from Ransack::Search object for search_form_with' do
|
1051
|
+
options = {}
|
1052
|
+
result = helper.send(:extract_search_and_set_url, search, options, 'search_form_with')
|
1053
|
+
expect(result).to eq(search)
|
1054
|
+
expect(options[:url]).to match(/people/)
|
1055
|
+
end
|
1056
|
+
|
1057
|
+
it 'extracts search from array with Search object for search_form_with' do
|
1058
|
+
options = {}
|
1059
|
+
comment_search = Comment.ransack
|
1060
|
+
result = helper.send(:extract_search_and_set_url, [:admin, comment_search], options, 'search_form_with')
|
1061
|
+
expect(result).to eq(comment_search)
|
1062
|
+
expect(options[:url]).to match(/admin/)
|
1063
|
+
end
|
1064
|
+
|
1065
|
+
it 'raises error for invalid record with correct method name for search_form_with' do
|
1066
|
+
options = {}
|
1067
|
+
expect {
|
1068
|
+
helper.send(:extract_search_and_set_url, "invalid", options, 'search_form_with')
|
1069
|
+
}.to raise_error(ArgumentError, 'No Ransack::Search object was provided to search_form_with!')
|
1070
|
+
end
|
1071
|
+
end
|
1072
|
+
end
|
859
1073
|
end
|
860
1074
|
end
|
861
1075
|
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Ransack
|
4
|
+
describe InvalidSearchError do
|
5
|
+
it 'inherits from ArgumentError' do
|
6
|
+
expect(InvalidSearchError.superclass).to eq(ArgumentError)
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'can be instantiated with a message' do
|
10
|
+
error = InvalidSearchError.new('Test error message')
|
11
|
+
expect(error.message).to eq('Test error message')
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'can be instantiated without a message' do
|
15
|
+
error = InvalidSearchError.new
|
16
|
+
expect(error.message).to eq('Ransack::InvalidSearchError')
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'can be raised and caught' do
|
20
|
+
expect { raise InvalidSearchError.new('Test') }.to raise_error(InvalidSearchError, 'Test')
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'can be raised and caught as ArgumentError' do
|
24
|
+
expect { raise InvalidSearchError.new('Test') }.to raise_error(ArgumentError, 'Test')
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|