ransack 2.4.2 → 4.0.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 (129) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/codeql.yml +72 -0
  3. data/.github/workflows/cronjob.yml +6 -9
  4. data/.github/workflows/deploy.yml +35 -0
  5. data/.github/workflows/rubocop.yml +1 -1
  6. data/.github/workflows/test-deploy.yml +29 -0
  7. data/.github/workflows/test.yml +22 -48
  8. data/.nojekyll +0 -0
  9. data/.rubocop.yml +3 -0
  10. data/CHANGELOG.md +208 -11
  11. data/CONTRIBUTING.md +41 -18
  12. data/Gemfile +10 -10
  13. data/README.md +44 -977
  14. data/bug_report_templates/test-ransacker-arel-present-predicate.rb +4 -0
  15. data/docs/.gitignore +19 -0
  16. data/docs/.nojekyll +0 -0
  17. data/docs/babel.config.js +3 -0
  18. data/docs/blog/2022-03-27-ransack-3.0.0.md +20 -0
  19. data/docs/docs/getting-started/_category_.json +4 -0
  20. data/docs/docs/getting-started/advanced-mode.md +46 -0
  21. data/docs/docs/getting-started/configuration.md +47 -0
  22. data/docs/docs/getting-started/search-matches.md +67 -0
  23. data/docs/docs/getting-started/simple-mode.md +288 -0
  24. data/docs/docs/getting-started/sorting.md +79 -0
  25. data/docs/docs/getting-started/using-predicates.md +282 -0
  26. data/docs/docs/going-further/_category_.json +4 -0
  27. data/docs/docs/going-further/acts-as-taggable-on.md +114 -0
  28. data/docs/docs/going-further/associations.md +70 -0
  29. data/docs/docs/going-further/custom-predicates.md +52 -0
  30. data/docs/docs/going-further/documentation.md +43 -0
  31. data/docs/docs/going-further/exporting-to-csv.md +49 -0
  32. data/docs/docs/going-further/external-guides.md +57 -0
  33. data/docs/docs/going-further/form-customisation.md +63 -0
  34. data/docs/docs/going-further/i18n.md +53 -0
  35. data/docs/docs/going-further/merging-searches.md +41 -0
  36. data/docs/docs/going-further/other-notes.md +428 -0
  37. data/docs/docs/going-further/polymorphic-search.md +40 -0
  38. data/docs/docs/going-further/ransackers.md +331 -0
  39. data/docs/docs/going-further/release_process.md +36 -0
  40. data/docs/docs/going-further/saving-queries.md +82 -0
  41. data/docs/docs/going-further/searching-postgres.md +57 -0
  42. data/docs/docs/going-further/wiki-contributors.md +82 -0
  43. data/docs/docs/intro.md +99 -0
  44. data/docs/docusaurus.config.js +120 -0
  45. data/docs/package.json +42 -0
  46. data/docs/sidebars.js +31 -0
  47. data/docs/src/components/HomepageFeatures/index.js +64 -0
  48. data/docs/src/components/HomepageFeatures/styles.module.css +11 -0
  49. data/docs/src/css/custom.css +39 -0
  50. data/docs/src/pages/index.module.css +23 -0
  51. data/docs/src/pages/markdown-page.md +7 -0
  52. data/docs/static/.nojekyll +0 -0
  53. data/docs/static/img/docusaurus.png +0 -0
  54. data/docs/static/img/favicon.ico +0 -0
  55. data/docs/static/img/logo.svg +1 -0
  56. data/docs/static/img/tutorial/docsVersionDropdown.png +0 -0
  57. data/docs/static/img/tutorial/localeDropdown.png +0 -0
  58. data/docs/static/img/undraw_docusaurus_mountain.svg +171 -0
  59. data/docs/static/img/undraw_docusaurus_react.svg +170 -0
  60. data/docs/static/img/undraw_docusaurus_tree.svg +40 -0
  61. data/docs/yarn.lock +8790 -0
  62. data/lib/polyamorous/activerecord_6.1_ruby_2/join_association.rb +0 -4
  63. data/lib/polyamorous/activerecord_6.1_ruby_2/join_dependency.rb +0 -1
  64. data/lib/polyamorous/activerecord_6.1_ruby_2/reflection.rb +11 -1
  65. data/lib/polyamorous/activerecord_7.1_ruby_2/join_association.rb +1 -0
  66. data/lib/polyamorous/activerecord_7.1_ruby_2/join_dependency.rb +1 -0
  67. data/lib/polyamorous/activerecord_7.1_ruby_2/reflection.rb +1 -0
  68. data/lib/ransack/adapters/active_record/base.rb +79 -10
  69. data/lib/ransack/adapters/active_record/context.rb +24 -51
  70. data/lib/ransack/configuration.rb +39 -12
  71. data/lib/ransack/constants.rb +125 -3
  72. data/lib/ransack/context.rb +34 -5
  73. data/lib/ransack/helpers/form_builder.rb +3 -3
  74. data/lib/ransack/helpers/form_helper.rb +14 -5
  75. data/lib/ransack/locale/sv.yml +70 -0
  76. data/lib/ransack/nodes/attribute.rb +2 -2
  77. data/lib/ransack/nodes/condition.rb +80 -7
  78. data/lib/ransack/nodes/grouping.rb +3 -3
  79. data/lib/ransack/nodes/node.rb +1 -1
  80. data/lib/ransack/nodes/sort.rb +2 -2
  81. data/lib/ransack/nodes/value.rb +2 -2
  82. data/lib/ransack/predicate.rb +1 -1
  83. data/lib/ransack/ransacker.rb +1 -1
  84. data/lib/ransack/search.rb +13 -7
  85. data/lib/ransack/translate.rb +3 -3
  86. data/lib/ransack/version.rb +1 -1
  87. data/lib/ransack/visitor.rb +38 -2
  88. data/lib/ransack.rb +3 -6
  89. data/ransack.gemspec +5 -5
  90. data/spec/helpers/polyamorous_helper.rb +2 -8
  91. data/spec/polyamorous/activerecord_compatibility_spec.rb +15 -0
  92. data/spec/polyamorous/join_association_spec.rb +1 -6
  93. data/spec/polyamorous/join_dependency_spec.rb +0 -16
  94. data/spec/ransack/adapters/active_record/base_spec.rb +101 -11
  95. data/spec/ransack/configuration_spec.rb +23 -9
  96. data/spec/ransack/helpers/form_builder_spec.rb +8 -8
  97. data/spec/ransack/helpers/form_helper_spec.rb +93 -4
  98. data/spec/ransack/nodes/condition_spec.rb +37 -0
  99. data/spec/ransack/nodes/value_spec.rb +115 -0
  100. data/spec/ransack/predicate_spec.rb +36 -1
  101. data/spec/ransack/search_spec.rb +140 -27
  102. data/spec/ransack/translate_spec.rb +1 -1
  103. data/spec/support/schema.rb +75 -9
  104. metadata +83 -37
  105. data/docs/release_process.md +0 -20
  106. data/lib/polyamorous/activerecord_5.2_ruby_2/join_association.rb +0 -24
  107. data/lib/polyamorous/activerecord_5.2_ruby_2/join_dependency.rb +0 -79
  108. data/lib/polyamorous/activerecord_5.2_ruby_2/reflection.rb +0 -11
  109. data/lib/polyamorous/activerecord_6.0_ruby_2/join_association.rb +0 -1
  110. data/lib/polyamorous/activerecord_6.0_ruby_2/join_dependency.rb +0 -80
  111. data/lib/polyamorous/activerecord_6.0_ruby_2/reflection.rb +0 -1
  112. data/lib/ransack/adapters/active_record/ransack/constants.rb +0 -128
  113. data/lib/ransack/adapters/active_record/ransack/context.rb +0 -56
  114. data/lib/ransack/adapters/active_record/ransack/nodes/condition.rb +0 -68
  115. data/lib/ransack/adapters/active_record/ransack/translate.rb +0 -8
  116. data/lib/ransack/adapters/active_record/ransack/visitor.rb +0 -47
  117. data/lib/ransack/adapters.rb +0 -64
  118. data/lib/ransack/nodes.rb +0 -8
  119. /data/docs/{img → docs/going-further/img}/create_release.png +0 -0
  120. /data/{logo → docs/static/logo}/ransack-h.png +0 -0
  121. /data/{logo → docs/static/logo}/ransack-h.svg +0 -0
  122. /data/{logo → docs/static/logo}/ransack-v.png +0 -0
  123. /data/{logo → docs/static/logo}/ransack-v.svg +0 -0
  124. /data/{logo → docs/static/logo}/ransack.png +0 -0
  125. /data/{logo → docs/static/logo}/ransack.svg +0 -0
  126. /data/lib/polyamorous/{activerecord_6.2_ruby_2 → activerecord_7.0_ruby_2}/join_association.rb +0 -0
  127. /data/lib/polyamorous/{activerecord_6.2_ruby_2 → activerecord_7.0_ruby_2}/join_dependency.rb +0 -0
  128. /data/lib/polyamorous/{activerecord_6.2_ruby_2 → activerecord_7.0_ruby_2}/reflection.rb +0 -0
  129. /data/lib/ransack/{adapters/active_record.rb → active_record.rb} +0 -0
@@ -8,7 +8,6 @@ module Ransack
8
8
  subject { ::ActiveRecord::Base }
9
9
 
10
10
  it { should respond_to :ransack }
11
- it { should respond_to :search }
12
11
 
13
12
  describe '#search' do
14
13
  subject { Person.ransack }
@@ -44,12 +43,12 @@ module Ransack
44
43
 
45
44
  it 'applies stringy boolean scopes with true value in an array' do
46
45
  s = Person.ransack('of_age' => ['true'])
47
- expect(s.result.to_sql).to (include 'age >= 18')
46
+ expect(s.result.to_sql).to (include rails7_and_mysql ? %q{(age >= '18')} : 'age >= 18')
48
47
  end
49
48
 
50
49
  it 'applies stringy boolean scopes with false value in an array' do
51
50
  s = Person.ransack('of_age' => ['false'])
52
- expect(s.result.to_sql).to (include 'age < 18')
51
+ expect(s.result.to_sql).to (include rails7_and_mysql ? %q{age < '18'} : 'age < 18')
53
52
  end
54
53
 
55
54
  it 'ignores unlisted scopes' do
@@ -69,15 +68,25 @@ module Ransack
69
68
 
70
69
  it 'passes values to scopes' do
71
70
  s = Person.ransack('over_age' => 18)
72
- expect(s.result.to_sql).to (include 'age > 18')
71
+ expect(s.result.to_sql).to (include rails7_and_mysql ? %q{age > '18'} : 'age > 18')
73
72
  end
74
73
 
75
74
  it 'chains scopes' do
76
75
  s = Person.ransack('over_age' => 18, 'active' => true)
77
- expect(s.result.to_sql).to (include 'age > 18')
76
+ expect(s.result.to_sql).to (include rails7_and_mysql ? %q{age > '18'} : 'age > 18')
78
77
  expect(s.result.to_sql).to (include 'active = 1')
79
78
  end
80
79
 
80
+ it 'applies scopes that define string SQL joins' do
81
+ allow(Article)
82
+ .to receive(:ransackable_scopes)
83
+ .and_return([:latest_comment_cont])
84
+
85
+ # Including a negative condition to test removing the scope
86
+ s = Search.new(Article, notes_note_not_eq: 'Test', latest_comment_cont: 'Test')
87
+ expect(s.result.to_sql).to include 'latest_comment'
88
+ end
89
+
81
90
  context "with sanitize_custom_scope_booleans set to false" do
82
91
  before(:all) do
83
92
  Ransack.configure { |c| c.sanitize_custom_scope_booleans = false }
@@ -89,12 +98,12 @@ module Ransack
89
98
 
90
99
  it 'passes true values to scopes' do
91
100
  s = Person.ransack('over_age' => 1)
92
- expect(s.result.to_sql).to (include 'age > 1')
101
+ expect(s.result.to_sql).to (include rails7_and_mysql ? %q{age > '1'} : 'age > 1')
93
102
  end
94
103
 
95
104
  it 'passes false values to scopes' do
96
105
  s = Person.ransack('over_age' => 0)
97
- expect(s.result.to_sql).to (include 'age > 0')
106
+ expect(s.result.to_sql).to (include rails7_and_mysql ? %q{age > '0'} : 'age > 0')
98
107
  end
99
108
  end
100
109
 
@@ -107,12 +116,12 @@ module Ransack
107
116
 
108
117
  it 'passes true values to scopes' do
109
118
  s = Person.ransack('over_age' => 1)
110
- expect(s.result.to_sql).to (include 'age > 1')
119
+ expect(s.result.to_sql).to (include rails7_and_mysql ? %q{age > '1'} : 'age > 1')
111
120
  end
112
121
 
113
122
  it 'passes false values to scopes' do
114
123
  s = Person.ransack('over_age' => 0)
115
- expect(s.result.to_sql).to (include 'age > 0')
124
+ expect(s.result.to_sql).to (include rails7_and_mysql ? %q{age > '0'} : 'age > 0')
116
125
  end
117
126
  end
118
127
 
@@ -123,7 +132,7 @@ module Ransack
123
132
  end
124
133
 
125
134
  it 'raises exception if ransack! called with unknown condition' do
126
- expect { Person.ransack!(unknown_attr_eq: 'Ernie') }.to raise_error
135
+ expect { Person.ransack!(unknown_attr_eq: 'Ernie') }.to raise_error(ArgumentError)
127
136
  end
128
137
 
129
138
  it 'does not modify the parameters' do
@@ -314,7 +323,11 @@ module Ransack
314
323
  end
315
324
 
316
325
  it 'should function correctly with a multi-parameter attribute' do
317
- ::ActiveRecord::Base.default_timezone = :utc
326
+ if ::ActiveRecord::VERSION::MAJOR >= 7
327
+ ::ActiveRecord.default_timezone = :utc
328
+ else
329
+ ::ActiveRecord::Base.default_timezone = :utc
330
+ end
318
331
  Time.zone = 'UTC'
319
332
 
320
333
  date = Date.current
@@ -644,6 +657,37 @@ module Ransack
644
657
  it { should_not include 'only_sort' }
645
658
  it { should include 'only_admin' }
646
659
  end
660
+
661
+ context 'when not defined in model, nor in ApplicationRecord' do
662
+ subject { Article.ransackable_attributes }
663
+
664
+ it "raises a helpful error" do
665
+ without_application_record_method(:ransackable_attributes) do
666
+ expect { subject }.to raise_error(RuntimeError, /Ransack needs Article attributes explicitly allowlisted/)
667
+ end
668
+ end
669
+ end
670
+
671
+ context 'when defined only in model by delegating to super' do
672
+ subject { Article.ransackable_attributes }
673
+
674
+ around do |example|
675
+ Article.singleton_class.define_method(:ransackable_attributes) do
676
+ super(nil) - super(nil)
677
+ end
678
+
679
+ example.run
680
+ ensure
681
+ Article.singleton_class.remove_method(:ransackable_attributes)
682
+ end
683
+
684
+ it "returns the allowlist in the model, and warns" do
685
+ without_application_record_method(:ransackable_attributes) do
686
+ expect { subject }.to output(/Ransack's builtin `ransackable_attributes` method is deprecated/).to_stderr
687
+ expect(subject).to be_empty
688
+ end
689
+ end
690
+ end
647
691
  end
648
692
 
649
693
  describe '#ransortable_attributes' do
@@ -676,6 +720,37 @@ module Ransack
676
720
  it { should include 'parent' }
677
721
  it { should include 'children' }
678
722
  it { should include 'articles' }
723
+
724
+ context 'when not defined in model, nor in ApplicationRecord' do
725
+ subject { Article.ransackable_associations }
726
+
727
+ it "raises a helpful error" do
728
+ without_application_record_method(:ransackable_associations) do
729
+ expect { subject }.to raise_error(RuntimeError, /Ransack needs Article associations explicitly allowlisted/)
730
+ end
731
+ end
732
+ end
733
+
734
+ context 'when defined only in model by delegating to super' do
735
+ subject { Article.ransackable_associations }
736
+
737
+ around do |example|
738
+ Article.singleton_class.define_method(:ransackable_associations) do
739
+ super(nil) - super(nil)
740
+ end
741
+
742
+ example.run
743
+ ensure
744
+ Article.singleton_class.remove_method(:ransackable_associations)
745
+ end
746
+
747
+ it "returns the allowlist in the model, and warns" do
748
+ without_application_record_method(:ransackable_associations) do
749
+ expect { subject }.to output(/Ransack's builtin `ransackable_associations` method is deprecated/).to_stderr
750
+ expect(subject).to be_empty
751
+ end
752
+ end
753
+ end
679
754
  end
680
755
 
681
756
  describe '#ransackable_scopes' do
@@ -690,6 +765,21 @@ module Ransack
690
765
  it { should eq [] }
691
766
  end
692
767
 
768
+ private
769
+
770
+ def without_application_record_method(method)
771
+ ApplicationRecord.singleton_class.alias_method :"original_#{method}", :"#{method}"
772
+ ApplicationRecord.singleton_class.remove_method :"#{method}"
773
+
774
+ yield
775
+ ensure
776
+ ApplicationRecord.singleton_class.alias_method :"#{method}", :"original_#{method}"
777
+ ApplicationRecord.singleton_class.remove_method :"original_#{method}"
778
+ end
779
+
780
+ def rails7_and_mysql
781
+ ::ActiveRecord::VERSION::MAJOR >= 7 && ENV['DB'] == 'mysql'
782
+ end
693
783
  end
694
784
  end
695
785
  end
@@ -20,7 +20,7 @@ module Ransack
20
20
  Ransack.configure do |config|
21
21
  config.add_predicate(
22
22
  :test_predicate_without_compound,
23
- :compounds => false
23
+ compounds: false
24
24
  )
25
25
  end
26
26
  expect(Ransack.predicates)
@@ -45,6 +45,20 @@ module Ransack
45
45
  Ransack.options = default
46
46
  end
47
47
 
48
+ it 'should have default value for strip_whitespace' do
49
+ expect(Ransack.options[:strip_whitespace]).to eq true
50
+ end
51
+
52
+ it 'changes default search key parameter' do
53
+ default = Ransack.options.clone
54
+
55
+ Ransack.configure { |c| c.strip_whitespace = false }
56
+
57
+ expect(Ransack.options[:strip_whitespace]).to eq false
58
+
59
+ Ransack.options = default
60
+ end
61
+
48
62
  it 'should have default values for arrows' do
49
63
  expect(Ransack.options[:up_arrow]).to eq '&#9660;'
50
64
  expect(Ransack.options[:down_arrow]).to eq '&#9650;'
@@ -124,8 +138,8 @@ module Ransack
124
138
  Ransack.configure do |config|
125
139
  config.add_predicate(
126
140
  :test_array_predicate,
127
- :wants_array => true,
128
- :compounds => true
141
+ wants_array: true,
142
+ compounds: true
129
143
  )
130
144
  end
131
145
 
@@ -139,11 +153,11 @@ module Ransack
139
153
  Ransack.configure do |config|
140
154
  config.add_predicate(
141
155
  :test_in_predicate,
142
- :arel_predicate => 'in'
156
+ arel_predicate: 'in'
143
157
  )
144
158
  config.add_predicate(
145
159
  :test_not_in_predicate,
146
- :arel_predicate => 'not_in'
160
+ arel_predicate: 'not_in'
147
161
  )
148
162
  end
149
163
 
@@ -157,13 +171,13 @@ module Ransack
157
171
  Ransack.configure do |config|
158
172
  config.add_predicate(
159
173
  :test_in_predicate_no_array,
160
- :arel_predicate => 'in',
161
- :wants_array => false
174
+ arel_predicate: 'in',
175
+ wants_array: false
162
176
  )
163
177
  config.add_predicate(
164
178
  :test_not_in_predicate_no_array,
165
- :arel_predicate => 'not_in',
166
- :wants_array => false
179
+ arel_predicate: 'not_in',
180
+ wants_array: false
167
181
  )
168
182
  end
169
183
 
@@ -26,7 +26,7 @@ module Ransack
26
26
  # @s.created_at_eq = date_values # This works in Rails 4.x but not 3.x
27
27
  @s.created_at_eq = [2011, 1, 2, 3, 4, 5] # so we have to do this
28
28
  html = @f.datetime_select(
29
- :created_at_eq, :use_month_numbers => true, :include_seconds => true
29
+ :created_at_eq, use_month_numbers: true, include_seconds: true
30
30
  )
31
31
  date_values.each { |val| expect(html).to include date_select_html(val) }
32
32
  end
@@ -70,13 +70,13 @@ module Ransack
70
70
 
71
71
  describe '#sort_link' do
72
72
  it 'sort_link for ransack attribute' do
73
- sort_link = @f.sort_link :name, :controller => 'people'
73
+ sort_link = @f.sort_link :name, controller: 'people'
74
74
  expect(sort_link).to match /people\?q(%5B|\[)s(%5D|\])=name\+asc/
75
75
  expect(sort_link).to match /sort_link/
76
76
  expect(sort_link).to match /Full Name<\/a>/
77
77
  end
78
78
  it 'sort_link for common attribute' do
79
- sort_link = @f.sort_link :id, :controller => 'people'
79
+ sort_link = @f.sort_link :id, controller: 'people'
80
80
  expect(sort_link).to match /id<\/a>/
81
81
  end
82
82
  end
@@ -99,14 +99,14 @@ module Ransack
99
99
  it 'returns ransackable attributes for associations with :associations' do
100
100
  attributes = Person.ransackable_attributes +
101
101
  Article.ransackable_attributes.map { |a| "articles_#{a}" }
102
- html = @f.attribute_select(:associations => ['articles'])
102
+ html = @f.attribute_select(associations: ['articles'])
103
103
  expect(html.split(/\n/).size).to eq(attributes.size)
104
104
  attributes.each do |attribute|
105
105
  expect(html).to match /<option value="#{attribute}">/
106
106
  end
107
107
  end
108
108
  it 'returns option groups for base and associations with :associations' do
109
- html = @f.attribute_select(:associations => ['articles'])
109
+ html = @f.attribute_select(associations: ['articles'])
110
110
  [Person, Article].each do |model|
111
111
  expect(html).to match /<optgroup label="#{model}">/
112
112
  end
@@ -121,19 +121,19 @@ module Ransack
121
121
  end
122
122
  end
123
123
  it 'filters predicates with single-value :only' do
124
- html = @f.predicate_select :only => 'eq'
124
+ html = @f.predicate_select only: 'eq'
125
125
  Predicate.names.reject { |k| k =~ /^eq/ }.each do |key|
126
126
  expect(html).not_to match /<option value="#{key}">/
127
127
  end
128
128
  end
129
129
  it 'filters predicates with multi-value :only' do
130
- html = @f.predicate_select :only => [:eq, :lt]
130
+ html = @f.predicate_select only: [:eq, :lt]
131
131
  Predicate.names.reject { |k| k =~ /^(eq|lt)/ }.each do |key|
132
132
  expect(html).not_to match /<option value="#{key}">/
133
133
  end
134
134
  end
135
135
  it 'excludes compounds when compounds: false' do
136
- html = @f.predicate_select :compounds => false
136
+ html = @f.predicate_select compounds: false
137
137
  Predicate.names.select { |k| k =~ /_(any|all)$/ }.each do |key|
138
138
  expect(html).not_to match /<option value="#{key}">/
139
139
  end
@@ -140,6 +140,32 @@ module Ransack
140
140
  }
141
141
  end
142
142
 
143
+ describe '#sort_link works even if search params are a string' do
144
+ before { @controller.view_context.params[:q] = 'input error' }
145
+ specify {
146
+ expect { @controller.view_context
147
+ .sort_link(
148
+ Person.ransack({}),
149
+ :name,
150
+ controller: 'people'
151
+ )
152
+ }.not_to raise_error
153
+ }
154
+ end
155
+
156
+ describe '#sort_url works even if search params are a string' do
157
+ before { @controller.view_context.params[:q] = 'input error' }
158
+ specify {
159
+ expect { @controller.view_context
160
+ .sort_url(
161
+ Person.ransack({}),
162
+ :name,
163
+ controller: 'people'
164
+ )
165
+ }.not_to raise_error
166
+ }
167
+ end
168
+
143
169
  describe '#sort_link with search_key defined as a string' do
144
170
  subject { @controller.view_context
145
171
  .sort_link(
@@ -469,13 +495,12 @@ module Ransack
469
495
  it { should match /exist\=existing/ }
470
496
  end
471
497
 
472
- context 'using a real ActionController::Parameter object',
473
- if: ::ActiveRecord::VERSION::MAJOR > 3 do
498
+ context 'using a real ActionController::Parameter object' do
474
499
 
475
500
  describe 'with symbol q:, #sort_link should include search params' do
476
501
  subject { @controller.view_context.sort_link(Person.ransack, :name) }
477
502
  let(:params) { ActionController::Parameters.new(
478
- { :q => { name_eq: 'TEST' }, controller: 'people' }
503
+ { q: { name_eq: 'TEST' }, controller: 'people' }
479
504
  ) }
480
505
  before { @controller.instance_variable_set(:@params, params) }
481
506
 
@@ -490,7 +515,7 @@ module Ransack
490
515
  describe 'with symbol q:, #sort_url should include search params' do
491
516
  subject { @controller.view_context.sort_url(Person.ransack, :name) }
492
517
  let(:params) { ActionController::Parameters.new(
493
- { :q => { name_eq: 'TEST' }, controller: 'people' }
518
+ { q: { name_eq: 'TEST' }, controller: 'people' }
494
519
  ) }
495
520
  before { @controller.instance_variable_set(:@params, params) }
496
521
 
@@ -727,6 +752,70 @@ module Ransack
727
752
  it { should match /Block label&nbsp;&#9660;/ }
728
753
  end
729
754
 
755
+ describe '#sort_link with class option' do
756
+ subject { @controller.view_context
757
+ .sort_link(
758
+ [:main_app, Person.ransack(sorts: ['name desc'])],
759
+ :name,
760
+ class: 'people', controller: 'people'
761
+ )
762
+ }
763
+ it { should match /class="sort_link desc people"/ }
764
+ it { should_not match /people\?class=people/ }
765
+ end
766
+
767
+ describe '#sort_link with class option workaround' do
768
+ it "generates a correct link and prints a deprecation" do
769
+ expect do
770
+ link = @controller.view_context
771
+ .sort_link(
772
+ [:main_app, Person.ransack(sorts: ['name desc'])],
773
+ :name,
774
+ 'name',
775
+ { controller: 'people' },
776
+ class: 'people'
777
+ )
778
+
779
+ expect(link).to match(/class="sort_link desc people"/)
780
+ expect(link).not_to match(/people\?class=people/)
781
+ end.to output(
782
+ /Passing two trailing hashes to `sort_link` is deprecated, merge the trailing hashes into a single one\. \(called at #{Regexp.escape(__FILE__)}:/
783
+ ).to_stderr
784
+ end
785
+ end
786
+
787
+ describe '#sort_link with data option' do
788
+ subject { @controller.view_context
789
+ .sort_link(
790
+ [:main_app, Person.ransack(sorts: ['name desc'])],
791
+ :name,
792
+ data: { turbo_action: :advance }, controller: 'people'
793
+ )
794
+ }
795
+ it { should match /data-turbo-action="advance"/ }
796
+ it { should_not match /people\?data%5Bturbo_action%5D=advance/ }
797
+ end
798
+
799
+ describe "#sort_link with host option" do
800
+ subject { @controller.view_context
801
+ .sort_link(
802
+ [:main_app, Person.ransack(sorts: ['name desc'])],
803
+ :name,
804
+ host: 'foo', controller: 'people'
805
+ )
806
+ }
807
+ it { should match /href="\/people\?q/ }
808
+ it { should_not match /href=".*foo/ }
809
+ end
810
+
811
+ describe "#sort_link ignores host in params" do
812
+ before { @controller.view_context.params[:host] = 'other_domain' }
813
+ subject { @controller.view_context.sort_link(Person.ransack, :name, controller: 'people') }
814
+
815
+ it { should match /href="\/people\?q/ }
816
+ it { should_not match /href=".*other_domain/ }
817
+ end
818
+
730
819
  describe '#search_form_for with default format' do
731
820
  subject { @controller.view_context
732
821
  .search_form_for(Person.ransack) {} }
@@ -3,6 +3,19 @@ require 'spec_helper'
3
3
  module Ransack
4
4
  module Nodes
5
5
  describe Condition do
6
+ context 'bug report #1245' do
7
+ it 'preserves tuple behavior' do
8
+ ransack_hash = {
9
+ m: 'and',
10
+ g: [
11
+ { title_type_in: ['["title 1", ""]'] }
12
+ ]
13
+ }
14
+
15
+ sql = Article.ransack(ransack_hash).result.to_sql
16
+ expect(sql).to include("IN (('title 1', ''))")
17
+ end
18
+ end
6
19
 
7
20
  context 'with an alias' do
8
21
  subject {
@@ -61,6 +74,30 @@ module Ransack
61
74
  specify { expect(subject).to be_nil }
62
75
  end
63
76
  end
77
+
78
+ context 'with an empty predicate' do
79
+ subject {
80
+ Condition.extract(
81
+ Context.for(Person), 'full_name', Person.first.name
82
+ )
83
+ }
84
+
85
+ context "when default_predicate = nil" do
86
+ before do
87
+ Ransack.configure { |c| c.default_predicate = nil }
88
+ end
89
+
90
+ specify { expect(subject).to be_nil }
91
+ end
92
+
93
+ context "when default_predicate = 'eq'" do
94
+ before do
95
+ Ransack.configure { |c| c.default_predicate = 'eq' }
96
+ end
97
+
98
+ specify { expect(subject).to eq Condition.extract(Context.for(Person), 'full_name_eq', Person.first.name) }
99
+ end
100
+ end
64
101
  end
65
102
  end
66
103
  end
@@ -0,0 +1,115 @@
1
+ require 'spec_helper'
2
+
3
+ module Ransack
4
+ module Nodes
5
+ describe Value do
6
+ let(:context) { Context.for(Person) }
7
+
8
+ subject do
9
+ Value.new(context, raw_value)
10
+ end
11
+
12
+ context "with a date value" do
13
+ let(:raw_value) { "2022-05-23" }
14
+
15
+ [:date].each do |type|
16
+ it "should cast #{type} correctly" do
17
+ result = subject.cast(type)
18
+
19
+ expect(result).to be_a_kind_of(Date)
20
+ expect(result).to eq(Date.parse(raw_value))
21
+ end
22
+ end
23
+ end
24
+
25
+ context "with a timestamp value" do
26
+ let(:raw_value) { "2022-05-23 10:40:02 -0400" }
27
+
28
+ [:datetime, :timestamp, :time, :timestamptz].each do |type|
29
+ it "should cast #{type} correctly" do
30
+ result = subject.cast(type)
31
+
32
+ expect(result).to be_a_kind_of(Time)
33
+ expect(result).to eq(Time.zone.parse(raw_value))
34
+ end
35
+ end
36
+ end
37
+
38
+ Constants::TRUE_VALUES.each do |value|
39
+ context "with a true boolean value (#{value})" do
40
+ let(:raw_value) { value.to_s }
41
+
42
+ it "should cast boolean correctly" do
43
+ result = subject.cast(:boolean)
44
+ expect(result).to eq(true)
45
+ end
46
+ end
47
+ end
48
+
49
+ Constants::FALSE_VALUES.each do |value|
50
+ context "with a false boolean value (#{value})" do
51
+ let(:raw_value) { value.to_s }
52
+
53
+ it "should cast boolean correctly" do
54
+ result = subject.cast(:boolean)
55
+
56
+ expect(result).to eq(false)
57
+ end
58
+ end
59
+ end
60
+
61
+ ["12", "101.5"].each do |value|
62
+ context "with an integer value (#{value})" do
63
+ let(:raw_value) { value }
64
+
65
+ it "should cast #{value} to integer correctly" do
66
+ result = subject.cast(:integer)
67
+
68
+ expect(result).to be_an(Integer)
69
+ expect(result).to eq(value.to_i)
70
+ end
71
+ end
72
+ end
73
+
74
+ ["12", "101.5"].each do |value|
75
+ context "with a float value (#{value})" do
76
+ let(:raw_value) { value }
77
+
78
+ it "should cast #{value} to float correctly" do
79
+ result = subject.cast(:float)
80
+
81
+ expect(result).to be_an(Float)
82
+ expect(result).to eq(value.to_f)
83
+ end
84
+ end
85
+ end
86
+
87
+ ["12", "101.5"].each do |value|
88
+ context "with a decimal value (#{value})" do
89
+ let(:raw_value) { value }
90
+
91
+ it "should cast #{value} to decimal correctly" do
92
+ result = subject.cast(:decimal)
93
+
94
+ expect(result).to be_a(BigDecimal)
95
+ expect(result).to eq(value.to_d)
96
+ end
97
+ end
98
+ end
99
+
100
+ ["12", "101.513"].each do |value|
101
+ context "with a money value (#{value})" do
102
+ let(:raw_value) { value }
103
+
104
+ it "should cast #{value} to money correctly" do
105
+ result = subject.cast(:money)
106
+
107
+ expect(result).to be_a(String)
108
+ expect(result).to eq(value.to_f.to_s)
109
+ end
110
+ end
111
+ end
112
+
113
+ end
114
+ end
115
+ end