ransack 4.1.1 → 4.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/README.md +5 -3
- data/lib/polyamorous/activerecord/join_association_7_2.rb +55 -0
- data/lib/polyamorous/polyamorous.rb +5 -1
- data/lib/ransack/adapters/active_record/context.rb +32 -5
- data/lib/ransack/constants.rb +1 -1
- data/lib/ransack/context.rb +7 -4
- data/lib/ransack/helpers/form_builder.rb +6 -7
- data/lib/ransack/helpers/form_helper.rb +86 -20
- data/lib/ransack/invalid_search_error.rb +3 -0
- data/lib/ransack/locale/ja.yml +51 -51
- data/lib/ransack/locale/ko.yml +70 -0
- data/lib/ransack/locale/uk.yml +72 -0
- data/lib/ransack/nodes/condition.rb +39 -7
- data/lib/ransack/nodes/grouping.rb +1 -1
- data/lib/ransack/nodes/sort.rb +1 -1
- data/lib/ransack/nodes/value.rb +9 -1
- data/lib/ransack/search.rb +4 -3
- data/lib/ransack/version.rb +1 -1
- data/lib/ransack.rb +9 -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 +106 -3
- 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/nodes/condition_spec.rb +230 -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 +16 -9
- data/spec/ransack/search_spec.rb +121 -1
- data/spec/ransack/translate_spec.rb +0 -1
- data/spec/spec_helper.rb +2 -3
- data/spec/support/schema.rb +42 -0
- metadata +17 -86
- 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 -99
- 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 -131
- data/.gitignore +0 -7
- data/.nojekyll +0 -0
- data/.rubocop.yml +0 -50
- data/CHANGELOG.md +0 -1176
- data/CONTRIBUTING.md +0 -171
- data/Gemfile +0 -53
- 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 -288
- 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 -428
- 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 -8879
- data/ransack.gemspec +0 -26
@@ -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
|
@@ -64,6 +64,7 @@ module Ransack
|
|
64
64
|
end
|
65
65
|
|
66
66
|
specify { expect { subject }.to raise_error ArgumentError }
|
67
|
+
specify { expect { subject }.to raise_error InvalidSearchError }
|
67
68
|
end
|
68
69
|
|
69
70
|
context "when ignore_unknown_conditions is true" do
|
@@ -98,6 +99,235 @@ module Ransack
|
|
98
99
|
specify { expect(subject).to eq Condition.extract(Context.for(Person), 'full_name_eq', Person.first.name) }
|
99
100
|
end
|
100
101
|
end
|
102
|
+
|
103
|
+
context 'with wildcard string values' do
|
104
|
+
it 'properly quotes values with wildcards for LIKE predicates' do
|
105
|
+
ransack_hash = { name_cont: 'test%' }
|
106
|
+
sql = Person.ransack(ransack_hash).result.to_sql
|
107
|
+
|
108
|
+
# The % should be properly quoted in the SQL
|
109
|
+
case ActiveRecord::Base.connection.adapter_name
|
110
|
+
when "Mysql2"
|
111
|
+
expect(sql).to include("LIKE '%test\\\\%%'")
|
112
|
+
expect(sql).not_to include("NOT LIKE '%test\\\\%%'")
|
113
|
+
when "PostGIS", "PostgreSQL"
|
114
|
+
expect(sql).to include("ILIKE '%test\\%%'")
|
115
|
+
expect(sql).not_to include("NOT ILIKE '%test\\%%'")
|
116
|
+
else
|
117
|
+
expect(sql).to include("LIKE '%test%%'")
|
118
|
+
expect(sql).not_to include("NOT LIKE '%test%%'")
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
it 'properly quotes values with wildcards for NOT LIKE predicates' do
|
123
|
+
ransack_hash = { name_not_cont: 'test%' }
|
124
|
+
sql = Person.ransack(ransack_hash).result.to_sql
|
125
|
+
|
126
|
+
# The % should be properly quoted in the SQL
|
127
|
+
case ActiveRecord::Base.connection.adapter_name
|
128
|
+
when "Mysql2"
|
129
|
+
expect(sql).to include("NOT LIKE '%test\\\\%%'")
|
130
|
+
when "PostGIS", "PostgreSQL"
|
131
|
+
expect(sql).to include("NOT ILIKE '%test\\%%'")
|
132
|
+
else
|
133
|
+
expect(sql).to include("NOT LIKE '%test%%'")
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
context 'with negative conditions on associations' do
|
139
|
+
it 'handles not_null predicate with true value correctly' do
|
140
|
+
ransack_hash = { comments_id_not_null: true }
|
141
|
+
sql = Person.ransack(ransack_hash).result.to_sql
|
142
|
+
|
143
|
+
# Should generate an IN query with IS NOT NULL condition
|
144
|
+
expect(sql).to include('IN (')
|
145
|
+
expect(sql).to include('IS NOT NULL')
|
146
|
+
expect(sql).not_to include('IS NULL')
|
147
|
+
end
|
148
|
+
|
149
|
+
it 'handles not_null predicate with false value correctly' do
|
150
|
+
ransack_hash = { comments_id_not_null: false }
|
151
|
+
sql = Person.ransack(ransack_hash).result.to_sql
|
152
|
+
|
153
|
+
# Should generate a NOT IN query with IS NULL condition
|
154
|
+
expect(sql).to include('NOT IN (')
|
155
|
+
expect(sql).to include('IS NULL')
|
156
|
+
expect(sql).not_to include('IS NOT NULL')
|
157
|
+
end
|
158
|
+
|
159
|
+
it 'handles not_cont predicate correctly' do
|
160
|
+
ransack_hash = { comments_body_not_cont: 'test' }
|
161
|
+
sql = Person.ransack(ransack_hash).result.to_sql
|
162
|
+
|
163
|
+
# Should generate a NOT IN query with LIKE condition (not NOT LIKE)
|
164
|
+
expect(sql).to include('NOT IN (')
|
165
|
+
expect(sql).to include("LIKE '%test%'")
|
166
|
+
expect(sql).not_to include("NOT LIKE '%test%'")
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
context 'with nested conditions' do
|
171
|
+
it 'correctly identifies non-nested conditions' do
|
172
|
+
condition = Condition.extract(
|
173
|
+
Context.for(Person), 'name_eq', 'Test'
|
174
|
+
)
|
175
|
+
|
176
|
+
# Create a mock parent table
|
177
|
+
parent_table = Person.arel_table
|
178
|
+
|
179
|
+
# Get the attribute name and make sure it starts with the table name
|
180
|
+
attribute = condition.attributes.first
|
181
|
+
expect(attribute.name).to eq('name')
|
182
|
+
expect(parent_table.name).to eq('people')
|
183
|
+
|
184
|
+
# The method should return false because 'name' doesn't start with 'people'
|
185
|
+
result = condition.send(:not_nested_condition, attribute, parent_table)
|
186
|
+
expect(result).to be false
|
187
|
+
end
|
188
|
+
|
189
|
+
it 'correctly identifies truly non-nested conditions when attribute name starts with table name' do
|
190
|
+
# Create a condition with an attribute that starts with the table name
|
191
|
+
condition = Condition.extract(
|
192
|
+
Context.for(Person), 'name_eq', 'Test'
|
193
|
+
)
|
194
|
+
|
195
|
+
# Modify the attribute name to start with the table name for testing purposes
|
196
|
+
attribute = condition.attributes.first
|
197
|
+
allow(attribute).to receive(:name).and_return('people_name')
|
198
|
+
|
199
|
+
# Create a parent table
|
200
|
+
parent_table = Person.arel_table
|
201
|
+
|
202
|
+
# Now the method should return true because 'people_name' starts with 'people'
|
203
|
+
result = condition.send(:not_nested_condition, attribute, parent_table)
|
204
|
+
expect(result).to be true
|
205
|
+
end
|
206
|
+
|
207
|
+
it 'correctly identifies nested conditions' do
|
208
|
+
condition = Condition.extract(
|
209
|
+
Context.for(Person), 'articles_title_eq', 'Test'
|
210
|
+
)
|
211
|
+
|
212
|
+
# Create a mock table alias
|
213
|
+
parent_table = Arel::Nodes::TableAlias.new(
|
214
|
+
Article.arel_table,
|
215
|
+
Article.arel_table
|
216
|
+
)
|
217
|
+
|
218
|
+
# Access the private method using send
|
219
|
+
result = condition.send(:not_nested_condition, condition.attributes.first, parent_table)
|
220
|
+
|
221
|
+
# Should return false for nested condition
|
222
|
+
expect(result).to be false
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
context 'with polymorphic associations and not_in predicate' do
|
227
|
+
before do
|
228
|
+
# Define test models for polymorphic associations
|
229
|
+
class ::TestTask < ActiveRecord::Base
|
230
|
+
self.table_name = 'tasks'
|
231
|
+
has_many :follows, primary_key: :uid, inverse_of: :followed, foreign_key: :followed_uid, class_name: 'TestFollow'
|
232
|
+
has_many :users, through: :follows, source: :follower, source_type: 'TestUser'
|
233
|
+
|
234
|
+
# Add ransackable_attributes method
|
235
|
+
def self.ransackable_attributes(auth_object = nil)
|
236
|
+
["created_at", "id", "name", "uid", "updated_at"]
|
237
|
+
end
|
238
|
+
|
239
|
+
# Add ransackable_associations method
|
240
|
+
def self.ransackable_associations(auth_object = nil)
|
241
|
+
["follows", "users"]
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
class ::TestFollow < ActiveRecord::Base
|
246
|
+
self.table_name = 'follows'
|
247
|
+
belongs_to :follower, polymorphic: true, foreign_key: :follower_uid, primary_key: :uid
|
248
|
+
belongs_to :followed, polymorphic: true, foreign_key: :followed_uid, primary_key: :uid
|
249
|
+
|
250
|
+
# Add ransackable_attributes method
|
251
|
+
def self.ransackable_attributes(auth_object = nil)
|
252
|
+
["created_at", "followed_type", "followed_uid", "follower_type", "follower_uid", "id", "updated_at"]
|
253
|
+
end
|
254
|
+
|
255
|
+
# Add ransackable_associations method
|
256
|
+
def self.ransackable_associations(auth_object = nil)
|
257
|
+
["followed", "follower"]
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
class ::TestUser < ActiveRecord::Base
|
262
|
+
self.table_name = 'users'
|
263
|
+
has_many :follows, primary_key: :uid, inverse_of: :follower, foreign_key: :follower_uid, class_name: 'TestFollow'
|
264
|
+
has_many :tasks, through: :follows, source: :followed, source_type: 'TestTask'
|
265
|
+
|
266
|
+
# Add ransackable_attributes method
|
267
|
+
def self.ransackable_attributes(auth_object = nil)
|
268
|
+
["created_at", "id", "name", "uid", "updated_at"]
|
269
|
+
end
|
270
|
+
|
271
|
+
# Add ransackable_associations method
|
272
|
+
def self.ransackable_associations(auth_object = nil)
|
273
|
+
["follows", "tasks"]
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
# Create tables if they don't exist
|
278
|
+
ActiveRecord::Base.connection.create_table(:tasks, force: true) do |t|
|
279
|
+
t.string :uid
|
280
|
+
t.string :name
|
281
|
+
t.timestamps null: false
|
282
|
+
end
|
283
|
+
|
284
|
+
ActiveRecord::Base.connection.create_table(:follows, force: true) do |t|
|
285
|
+
t.string :followed_uid, null: false
|
286
|
+
t.string :followed_type, null: false
|
287
|
+
t.string :follower_uid, null: false
|
288
|
+
t.string :follower_type, null: false
|
289
|
+
t.timestamps null: false
|
290
|
+
t.index [:followed_uid, :followed_type]
|
291
|
+
t.index [:follower_uid, :follower_type]
|
292
|
+
end
|
293
|
+
|
294
|
+
ActiveRecord::Base.connection.create_table(:users, force: true) do |t|
|
295
|
+
t.string :uid
|
296
|
+
t.string :name
|
297
|
+
t.timestamps null: false
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
301
|
+
after do
|
302
|
+
# Clean up test models and tables
|
303
|
+
Object.send(:remove_const, :TestTask)
|
304
|
+
Object.send(:remove_const, :TestFollow)
|
305
|
+
Object.send(:remove_const, :TestUser)
|
306
|
+
|
307
|
+
ActiveRecord::Base.connection.drop_table(:tasks, if_exists: true)
|
308
|
+
ActiveRecord::Base.connection.drop_table(:follows, if_exists: true)
|
309
|
+
ActiveRecord::Base.connection.drop_table(:users, if_exists: true)
|
310
|
+
end
|
311
|
+
|
312
|
+
it 'correctly handles not_in predicate with polymorphic associations' do
|
313
|
+
# Create the search
|
314
|
+
search = TestTask.ransack(users_uid_not_in: ['uid_example'])
|
315
|
+
sql = search.result.to_sql
|
316
|
+
|
317
|
+
# Verify the SQL contains the expected NOT IN clause
|
318
|
+
expect(sql).to include('NOT IN')
|
319
|
+
expect(sql).to include("follower_uid")
|
320
|
+
expect(sql).to include("followed_uid")
|
321
|
+
expect(sql).to include("'uid_example'")
|
322
|
+
|
323
|
+
# The SQL should include a reference to tasks.uid
|
324
|
+
expect(sql).to include("tasks")
|
325
|
+
expect(sql).to include("uid")
|
326
|
+
|
327
|
+
# The SQL should include a reference to follows table
|
328
|
+
expect(sql).to include("follows")
|
329
|
+
end
|
330
|
+
end
|
101
331
|
end
|
102
332
|
end
|
103
333
|
end
|
@@ -3,7 +3,6 @@ require 'spec_helper'
|
|
3
3
|
module Ransack
|
4
4
|
module Nodes
|
5
5
|
describe Grouping do
|
6
|
-
|
7
6
|
before do
|
8
7
|
@g = 1
|
9
8
|
end
|
@@ -66,6 +65,7 @@ module Ransack
|
|
66
65
|
}
|
67
66
|
}
|
68
67
|
end
|
68
|
+
|
69
69
|
before { subject.conditions = conditions }
|
70
70
|
|
71
71
|
it 'expect duplicates to be removed' do
|
@@ -98,6 +98,7 @@ module Ransack
|
|
98
98
|
}
|
99
99
|
}
|
100
100
|
end
|
101
|
+
|
101
102
|
before { subject.conditions = conditions }
|
102
103
|
|
103
104
|
it 'expect them to be parsed as different and not as duplicates' do
|
@@ -105,7 +106,6 @@ module Ransack
|
|
105
106
|
end
|
106
107
|
end
|
107
108
|
end
|
108
|
-
|
109
109
|
end
|
110
110
|
end
|
111
111
|
end
|
@@ -71,6 +71,18 @@ module Ransack
|
|
71
71
|
end
|
72
72
|
end
|
73
73
|
|
74
|
+
[[], ["12"], ["101.5"]].each do |value|
|
75
|
+
context "with an array value (#{value.inspect})" do
|
76
|
+
let(:raw_value) { value }
|
77
|
+
|
78
|
+
it "should cast to integer as nil" do
|
79
|
+
result = subject.cast(:integer)
|
80
|
+
|
81
|
+
expect(result).to be nil
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
74
86
|
["12", "101.5"].each do |value|
|
75
87
|
context "with a float value (#{value})" do
|
76
88
|
let(:raw_value) { value }
|
@@ -109,7 +121,6 @@ module Ransack
|
|
109
121
|
end
|
110
122
|
end
|
111
123
|
end
|
112
|
-
|
113
124
|
end
|
114
125
|
end
|
115
126
|
end
|
@@ -5,7 +5,6 @@ module Ransack
|
|
5
5
|
FALSE_VALUES = [false, 0, '0', 'f', 'F', 'false', 'FALSE'].to_set
|
6
6
|
|
7
7
|
describe Predicate do
|
8
|
-
|
9
8
|
before do
|
10
9
|
@s = Search.new(Person)
|
11
10
|
end
|
@@ -158,9 +157,10 @@ module Ransack
|
|
158
157
|
|
159
158
|
describe 'cont' do
|
160
159
|
it_has_behavior 'wildcard escaping', :name_cont,
|
161
|
-
(
|
160
|
+
(case ActiveRecord::Base.connection.adapter_name
|
161
|
+
when "PostGIS", "PostgreSQL"
|
162
162
|
/"people"."name" ILIKE '%\\%\\.\\_\\\\%'/
|
163
|
-
|
163
|
+
when "Mysql2"
|
164
164
|
/`people`.`name` LIKE '%\\\\%.\\\\_\\\\\\\\%'/
|
165
165
|
else
|
166
166
|
/"people"."name" LIKE '%%._\\%'/
|
@@ -177,9 +177,10 @@ module Ransack
|
|
177
177
|
|
178
178
|
describe 'not_cont' do
|
179
179
|
it_has_behavior 'wildcard escaping', :name_not_cont,
|
180
|
-
(
|
180
|
+
(case ActiveRecord::Base.connection.adapter_name
|
181
|
+
when "PostGIS", "PostgreSQL"
|
181
182
|
/"people"."name" NOT ILIKE '%\\%\\.\\_\\\\%'/
|
182
|
-
|
183
|
+
when "Mysql2"
|
183
184
|
/`people`.`name` NOT LIKE '%\\\\%.\\\\_\\\\\\\\%'/
|
184
185
|
else
|
185
186
|
/"people"."name" NOT LIKE '%%._\\%'/
|
@@ -196,9 +197,12 @@ module Ransack
|
|
196
197
|
|
197
198
|
describe 'i_cont' do
|
198
199
|
it_has_behavior 'wildcard escaping', :name_i_cont,
|
199
|
-
(
|
200
|
+
(case ActiveRecord::Base.connection.adapter_name
|
201
|
+
when "PostGIS"
|
202
|
+
/LOWER\("people"."name"\) ILIKE '%\\%\\.\\_\\\\%'/
|
203
|
+
when "PostgreSQL"
|
200
204
|
/"people"."name" ILIKE '%\\%\\.\\_\\\\%'/
|
201
|
-
|
205
|
+
when "Mysql2"
|
202
206
|
/LOWER\(`people`.`name`\) LIKE '%\\\\%.\\\\_\\\\\\\\%'/
|
203
207
|
else
|
204
208
|
/LOWER\("people"."name"\) LIKE '%%._\\%'/
|
@@ -215,9 +219,12 @@ module Ransack
|
|
215
219
|
|
216
220
|
describe 'not_i_cont' do
|
217
221
|
it_has_behavior 'wildcard escaping', :name_not_i_cont,
|
218
|
-
(
|
222
|
+
(case ActiveRecord::Base.connection.adapter_name
|
223
|
+
when "PostGIS"
|
224
|
+
/LOWER\("people"."name"\) NOT ILIKE '%\\%\\.\\_\\\\%'/
|
225
|
+
when "PostgreSQL"
|
219
226
|
/"people"."name" NOT ILIKE '%\\%\\.\\_\\\\%'/
|
220
|
-
|
227
|
+
when "Mysql2"
|
221
228
|
/LOWER\(`people`.`name`\) NOT LIKE '%\\\\%.\\\\_\\\\\\\\%'/
|
222
229
|
else
|
223
230
|
/LOWER\("people"."name"\) NOT LIKE '%%._\\%'/
|