ransack 2.1.1 → 2.5.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 (88) hide show
  1. checksums.yaml +4 -4
  2. data/.github/FUNDING.yml +3 -0
  3. data/.github/SECURITY.md +12 -0
  4. data/.github/workflows/cronjob.yml +102 -0
  5. data/.github/workflows/rubocop.yml +20 -0
  6. data/.github/workflows/test.yml +163 -0
  7. data/.gitignore +1 -0
  8. data/.rubocop.yml +44 -0
  9. data/CHANGELOG.md +64 -1
  10. data/CONTRIBUTING.md +16 -11
  11. data/Gemfile +23 -17
  12. data/README.md +190 -57
  13. data/bug_report_templates/test-ransack-scope-and-column-same-name.rb +78 -0
  14. data/bug_report_templates/test-ransacker-arel-present-predicate.rb +71 -0
  15. data/docs/img/create_release.png +0 -0
  16. data/docs/release_process.md +17 -0
  17. data/lib/polyamorous/{activerecord_5.2.1_ruby_2 → activerecord_5.2_ruby_2}/join_association.rb +2 -9
  18. data/lib/polyamorous/{activerecord_5.2.1_ruby_2 → activerecord_5.2_ruby_2}/join_dependency.rb +25 -3
  19. data/lib/polyamorous/activerecord_5.2_ruby_2/reflection.rb +11 -0
  20. data/lib/polyamorous/activerecord_6.0_ruby_2/join_association.rb +1 -0
  21. data/lib/polyamorous/activerecord_6.0_ruby_2/join_dependency.rb +80 -0
  22. data/lib/polyamorous/activerecord_6.0_ruby_2/reflection.rb +1 -0
  23. data/lib/polyamorous/activerecord_6.1_ruby_2/join_association.rb +74 -0
  24. data/lib/polyamorous/activerecord_6.1_ruby_2/join_dependency.rb +93 -0
  25. data/lib/polyamorous/activerecord_6.1_ruby_2/reflection.rb +1 -0
  26. data/lib/polyamorous/activerecord_7.0_ruby_2/join_association.rb +1 -0
  27. data/lib/polyamorous/activerecord_7.0_ruby_2/join_dependency.rb +1 -0
  28. data/lib/polyamorous/activerecord_7.0_ruby_2/reflection.rb +1 -0
  29. data/lib/polyamorous/polyamorous.rb +24 -0
  30. data/lib/polyamorous.rb +1 -25
  31. data/lib/ransack/adapters/active_record/base.rb +5 -1
  32. data/lib/ransack/adapters/active_record/context.rb +71 -68
  33. data/lib/ransack/adapters/active_record/ransack/constants.rb +18 -3
  34. data/lib/ransack/adapters/active_record/ransack/context.rb +2 -6
  35. data/lib/ransack/adapters/active_record/ransack/nodes/condition.rb +13 -5
  36. data/lib/ransack/adapters/active_record/ransack/translate.rb +1 -1
  37. data/lib/ransack/configuration.rb +31 -1
  38. data/lib/ransack/constants.rb +3 -5
  39. data/lib/ransack/context.rb +19 -18
  40. data/lib/ransack/helpers/form_builder.rb +8 -14
  41. data/lib/ransack/helpers/form_helper.rb +1 -1
  42. data/lib/ransack/helpers.rb +1 -1
  43. data/lib/ransack/locale/az.yml +1 -1
  44. data/lib/ransack/locale/ca.yml +70 -0
  45. data/lib/ransack/locale/es.yml +22 -22
  46. data/lib/ransack/locale/fa.yml +70 -0
  47. data/lib/ransack/locale/fi.yml +71 -0
  48. data/lib/ransack/locale/sk.yml +70 -0
  49. data/lib/ransack/locale/sv.yml +70 -0
  50. data/lib/ransack/nodes/attribute.rb +1 -1
  51. data/lib/ransack/nodes/condition.rb +7 -1
  52. data/lib/ransack/nodes/grouping.rb +1 -1
  53. data/lib/ransack/nodes/sort.rb +3 -3
  54. data/lib/ransack/nodes/value.rb +1 -1
  55. data/lib/ransack/predicate.rb +2 -1
  56. data/lib/ransack/search.rb +4 -1
  57. data/lib/ransack/translate.rb +115 -115
  58. data/lib/ransack/version.rb +1 -1
  59. data/lib/ransack.rb +3 -3
  60. data/ransack.gemspec +8 -23
  61. data/spec/blueprints/articles.rb +1 -1
  62. data/spec/blueprints/comments.rb +1 -1
  63. data/spec/blueprints/notes.rb +1 -1
  64. data/spec/blueprints/tags.rb +1 -1
  65. data/spec/console.rb +5 -5
  66. data/spec/helpers/polyamorous_helper.rb +3 -8
  67. data/spec/helpers/ransack_helper.rb +1 -1
  68. data/spec/{ransack → polyamorous}/join_association_spec.rb +8 -1
  69. data/spec/{ransack → polyamorous}/join_dependency_spec.rb +18 -7
  70. data/spec/{ransack → polyamorous}/join_spec.rb +0 -0
  71. data/spec/ransack/adapters/active_record/base_spec.rb +26 -15
  72. data/spec/ransack/adapters/active_record/context_spec.rb +60 -18
  73. data/spec/ransack/configuration_spec.rb +24 -0
  74. data/spec/ransack/helpers/form_helper_spec.rb +16 -16
  75. data/spec/ransack/nodes/condition_spec.rb +13 -0
  76. data/spec/ransack/nodes/grouping_spec.rb +2 -2
  77. data/spec/ransack/predicate_spec.rb +54 -2
  78. data/spec/ransack/search_spec.rb +238 -36
  79. data/spec/spec_helper.rb +10 -5
  80. data/spec/support/schema.rb +37 -3
  81. metadata +45 -139
  82. data/.travis.yml +0 -37
  83. data/lib/polyamorous/activerecord_5.0_ruby_2/join_association.rb +0 -2
  84. data/lib/polyamorous/activerecord_5.0_ruby_2/join_dependency.rb +0 -2
  85. data/lib/polyamorous/activerecord_5.1_ruby_2/join_association.rb +0 -32
  86. data/lib/polyamorous/activerecord_5.1_ruby_2/join_dependency.rb +0 -112
  87. data/lib/polyamorous/activerecord_5.2.0_ruby_2/join_association.rb +0 -32
  88. data/lib/polyamorous/activerecord_5.2.0_ruby_2/join_dependency.rb +0 -113
@@ -44,12 +44,12 @@ module Ransack
44
44
 
45
45
  it 'applies stringy boolean scopes with true value in an array' do
46
46
  s = Person.ransack('of_age' => ['true'])
47
- expect(s.result.to_sql).to (include 'age >= 18')
47
+ expect(s.result.to_sql).to (include rails7_and_mysql ? %q{(age >= '18')} : 'age >= 18')
48
48
  end
49
49
 
50
50
  it 'applies stringy boolean scopes with false value in an array' do
51
51
  s = Person.ransack('of_age' => ['false'])
52
- expect(s.result.to_sql).to (include 'age < 18')
52
+ expect(s.result.to_sql).to (include rails7_and_mysql ? %q{age < '18'} : 'age < 18')
53
53
  end
54
54
 
55
55
  it 'ignores unlisted scopes' do
@@ -69,12 +69,12 @@ module Ransack
69
69
 
70
70
  it 'passes values to scopes' do
71
71
  s = Person.ransack('over_age' => 18)
72
- expect(s.result.to_sql).to (include 'age > 18')
72
+ expect(s.result.to_sql).to (include rails7_and_mysql ? %q{age > '18'} : 'age > 18')
73
73
  end
74
74
 
75
75
  it 'chains scopes' do
76
76
  s = Person.ransack('over_age' => 18, 'active' => true)
77
- expect(s.result.to_sql).to (include 'age > 18')
77
+ expect(s.result.to_sql).to (include rails7_and_mysql ? %q{age > '18'} : 'age > 18')
78
78
  expect(s.result.to_sql).to (include 'active = 1')
79
79
  end
80
80
 
@@ -89,12 +89,12 @@ module Ransack
89
89
 
90
90
  it 'passes true values to scopes' do
91
91
  s = Person.ransack('over_age' => 1)
92
- expect(s.result.to_sql).to (include 'age > 1')
92
+ expect(s.result.to_sql).to (include rails7_and_mysql ? %q{age > '1'} : 'age > 1')
93
93
  end
94
94
 
95
95
  it 'passes false values to scopes' do
96
96
  s = Person.ransack('over_age' => 0)
97
- expect(s.result.to_sql).to (include 'age > 0')
97
+ expect(s.result.to_sql).to (include rails7_and_mysql ? %q{age > '0'} : 'age > 0')
98
98
  end
99
99
  end
100
100
 
@@ -107,12 +107,12 @@ module Ransack
107
107
 
108
108
  it 'passes true values to scopes' do
109
109
  s = Person.ransack('over_age' => 1)
110
- expect(s.result.to_sql).to (include 'age > 1')
110
+ expect(s.result.to_sql).to (include rails7_and_mysql ? %q{age > '1'} : 'age > 1')
111
111
  end
112
112
 
113
113
  it 'passes false values to scopes' do
114
114
  s = Person.ransack('over_age' => 0)
115
- expect(s.result.to_sql).to (include 'age > 0')
115
+ expect(s.result.to_sql).to (include rails7_and_mysql ? %q{age > '0'} : 'age > 0')
116
116
  end
117
117
  end
118
118
 
@@ -122,6 +122,10 @@ module Ransack
122
122
  expect { Person.ransack('') }.to_not raise_error
123
123
  end
124
124
 
125
+ it 'raises exception if ransack! called with unknown condition' do
126
+ expect { Person.ransack!(unknown_attr_eq: 'Ernie') }.to raise_error(ArgumentError)
127
+ end
128
+
125
129
  it 'does not modify the parameters' do
126
130
  params = { name_eq: '' }
127
131
  expect { Person.ransack(params) }.not_to change { params }
@@ -143,14 +147,12 @@ module Ransack
143
147
  it 'removes redundant joins from top query' do
144
148
  s = Article.ransack(tags_name_not_eq: "Fantasy")
145
149
  sql = s.result.to_sql
146
-
147
150
  expect(sql).to_not include('LEFT OUTER JOIN')
148
151
  end
149
152
 
150
153
  it 'handles != for single values' do
151
154
  s = Article.ransack(tags_name_not_eq: "Fantasy")
152
155
  articles = s.result.to_a
153
-
154
156
  expect(articles).to include marco
155
157
  expect(articles).to_not include arthur
156
158
  end
@@ -267,10 +269,12 @@ module Ransack
267
269
  # end
268
270
 
269
271
  it 'creates ransack attributes' do
272
+ person = Person.create!(name: 'Aric Smith')
273
+
270
274
  s = Person.ransack(reversed_name_eq: 'htimS cirA')
271
275
  expect(s.result.size).to eq(1)
272
276
 
273
- expect(s.result.first).to eq Person.where(name: 'Aric Smith').first
277
+ expect(s.result.first).to eq person
274
278
  end
275
279
 
276
280
  it 'can be accessed through associations' do
@@ -310,7 +314,11 @@ module Ransack
310
314
  end
311
315
 
312
316
  it 'should function correctly with a multi-parameter attribute' do
313
- ::ActiveRecord::Base.default_timezone = :utc
317
+ if ::ActiveRecord::VERSION::MAJOR >= 7
318
+ ::ActiveRecord.default_timezone = :utc
319
+ else
320
+ ::ActiveRecord::Base.default_timezone = :utc
321
+ end
314
322
  Time.zone = 'UTC'
315
323
 
316
324
  date = Date.current
@@ -460,9 +468,9 @@ module Ransack
460
468
  Comment.create(article: Article.create(title: 'Avenger'), person: Person.create(salary: 100_000)),
461
469
  Comment.create(article: Article.create(title: 'Avenge'), person: Person.create(salary: 50_000)),
462
470
  ]
463
- expect(Comment.ransack(article_title_cont: 'aven',s: 'person_salary desc').result).to eq(comments)
471
+ expect(Comment.ransack(article_title_cont: 'aven', s: 'person_salary desc').result).to eq(comments)
464
472
  expect(Comment.joins(:person).ransack(s: 'persons_salarydesc', article_title_cont: 'aven').result).to eq(comments)
465
- expect(Comment.joins(:person).ransack(article_title_cont: 'aven',s: 'persons_salary desc').result).to eq(comments)
473
+ expect(Comment.joins(:person).ransack(article_title_cont: 'aven', s: 'persons_salary desc').result).to eq(comments)
466
474
  end
467
475
 
468
476
  it 'allows sort by `only_sort` field' do
@@ -541,7 +549,6 @@ module Ransack
541
549
  )
542
550
  end
543
551
 
544
-
545
552
  it 'should allow passing ransacker arguments to a ransacker' do
546
553
  s = Person.ransack(
547
554
  c: [{
@@ -687,6 +694,10 @@ module Ransack
687
694
  it { should eq [] }
688
695
  end
689
696
 
697
+ private
698
+ def rails7_and_mysql
699
+ ::ActiveRecord::VERSION::MAJOR >= 7 && ENV['DB'] == 'mysql'
700
+ end
690
701
  end
691
702
  end
692
703
  end
@@ -9,7 +9,6 @@ module Ransack
9
9
  describe Context do
10
10
  subject { Context.new(Person) }
11
11
 
12
-
13
12
  it 'has an Active Record alias tracker method' do
14
13
  expect(subject.alias_tracker)
15
14
  .to be_an ::ActiveRecord::Associations::AliasTracker
@@ -40,6 +39,66 @@ module Ransack
40
39
  end
41
40
  end
42
41
 
42
+ describe '#build_correlated_subquery' do
43
+ it 'build correlated subquery for Root STI model' do
44
+ search = Search.new(Person, { articles_title_not_eq: 'some_title' }, context: subject)
45
+ attribute = search.conditions.first.attributes.first
46
+ constraints = subject.build_correlated_subquery(attribute.parent).constraints
47
+ constraint = constraints.first
48
+
49
+ expect(constraints.length).to eql 1
50
+ expect(constraint.left.name).to eql 'person_id'
51
+ expect(constraint.left.relation.name).to eql 'articles'
52
+ expect(constraint.right.name).to eql 'id'
53
+ expect(constraint.right.relation.name).to eql 'people'
54
+ end
55
+
56
+ it 'build correlated subquery for Child STI model when predicate is not_eq' do
57
+ search = Search.new(Person, { story_articles_title_not_eq: 'some_title' }, context: subject)
58
+ attribute = search.conditions.first.attributes.first
59
+ constraints = subject.build_correlated_subquery(attribute.parent).constraints
60
+ constraint = constraints.first
61
+
62
+ expect(constraints.length).to eql 1
63
+ expect(constraint.left.relation.name).to eql 'articles'
64
+ expect(constraint.left.name).to eql 'person_id'
65
+ expect(constraint.right.relation.name).to eql 'people'
66
+ expect(constraint.right.name).to eql 'id'
67
+ end
68
+
69
+ it 'build correlated subquery for Child STI model when predicate is eq' do
70
+ search = Search.new(Person, { story_articles_title_not_eq: 'some_title' }, context: subject)
71
+ attribute = search.conditions.first.attributes.first
72
+ constraints = subject.build_correlated_subquery(attribute.parent).constraints
73
+ constraint = constraints.first
74
+
75
+ expect(constraints.length).to eql 1
76
+ expect(constraint.left.relation.name).to eql 'articles'
77
+ expect(constraint.left.name).to eql 'person_id'
78
+ expect(constraint.right.relation.name).to eql 'people'
79
+ expect(constraint.right.name).to eql 'id'
80
+ end
81
+
82
+ it 'build correlated subquery for multiple conditions (default scope)' do
83
+ search = Search.new(Person, { comments_body_not_eq: 'some_title' })
84
+
85
+ # Was
86
+ # SELECT "people".* FROM "people" WHERE "people"."id" NOT IN (
87
+ # SELECT "comments"."disabled" FROM "comments"
88
+ # WHERE "comments"."disabled" = "people"."id"
89
+ # AND NOT ("comments"."body" != 'some_title')
90
+ # ) ORDER BY "people"."id" DESC
91
+ # Should Be
92
+ # SELECT "people".* FROM "people" WHERE "people"."id" NOT IN (
93
+ # SELECT "comments"."person_id" FROM "comments"
94
+ # WHERE "comments"."person_id" = "people"."id"
95
+ # AND NOT ("comments"."body" != 'some_title')
96
+ # ) ORDER BY "people"."id" DESC
97
+
98
+ expect(search.result.to_sql).to match /.comments.\..person_id. = .people.\..id./
99
+ end
100
+ end
101
+
43
102
  describe 'sharing context across searches' do
44
103
  let(:shared_context) { Context.for(Person) }
45
104
 
@@ -50,23 +109,6 @@ module Ransack
50
109
  context: shared_context)
51
110
  end
52
111
 
53
- describe '#join_associations', if: AR_version <= '4.0' do
54
- it 'returns dependent join associations for all searches run
55
- against the context' do
56
- parents, children = shared_context.join_associations
57
-
58
- expect(children.aliased_table_name).to eq "children_people"
59
- expect(parents.aliased_table_name).to eq "parents_people"
60
- end
61
-
62
- it 'can be rejoined to execute a valid query' do
63
- parents, children = shared_context.join_associations
64
-
65
- expect { Person.joins(parents).joins(children).to_a }
66
- .to_not raise_error
67
- end
68
- end
69
-
70
112
  describe '#join_sources' do
71
113
  it 'returns dependent arel join nodes for all searches run against
72
114
  the context' do
@@ -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;'
@@ -173,5 +187,15 @@ module Ransack
173
187
  .to eq false
174
188
  end
175
189
  end
190
+
191
+ it "PG's sort option", if: ::ActiveRecord::Base.connection.adapter_name == "PostgreSQL" do
192
+ default = Ransack.options.clone
193
+
194
+ Ransack.configure { |c| c.postgres_fields_sort_option = :nulls_first }
195
+
196
+ expect(Ransack.options[:postgres_fields_sort_option]).to eq :nulls_first
197
+
198
+ Ransack.options = default
199
+ end
176
200
  end
177
201
  end
@@ -186,7 +186,7 @@ module Ransack
186
186
  )
187
187
  }
188
188
  it {
189
- should match( /people\?q(%5B|\[)s(%5D|\])(%5B|\[)(%5D|\])=name\+asc&amp;q(%5B|\[)s(%5D|\])(%5B|\[)(%5D|\])=email\+desc/
189
+ should match(/people\?q(%5B|\[)s(%5D|\])(%5B|\[)(%5D|\])=name\+asc&amp;q(%5B|\[)s(%5D|\])(%5B|\[)(%5D|\])=email\+desc/
190
190
  )
191
191
  }
192
192
  it { should match /sort_link desc/ }
@@ -202,7 +202,7 @@ module Ransack
202
202
  )
203
203
  }
204
204
  it {
205
- should match( /people\?q(%5B|\[)s(%5D|\])(%5B|\[)(%5D|\])=name\+asc&q(%5B|\[)s(%5D|\])(%5B|\[)(%5D|\])=email\+desc/
205
+ should match(/people\?q(%5B|\[)s(%5D|\])(%5B|\[)(%5D|\])=name\+asc&q(%5B|\[)s(%5D|\])(%5B|\[)(%5D|\])=email\+desc/
206
206
  )
207
207
  }
208
208
  end
@@ -216,7 +216,7 @@ module Ransack
216
216
  )
217
217
  }
218
218
  it {
219
- should match( /people\?q(%5B|\[)s(%5D|\])(%5B|\[)(%5D|\])=name\+asc&amp;q(%5B|\[)s(%5D|\])(%5B|\[)(%5D|\])=email\+desc/
219
+ should match(/people\?q(%5B|\[)s(%5D|\])(%5B|\[)(%5D|\])=name\+asc&amp;q(%5B|\[)s(%5D|\])(%5B|\[)(%5D|\])=email\+desc/
220
220
  )
221
221
  }
222
222
  it { should match /sort_link desc/ }
@@ -232,7 +232,7 @@ module Ransack
232
232
  )
233
233
  }
234
234
  it {
235
- should match( /people\?q(%5B|\[)s(%5D|\])(%5B|\[)(%5D|\])=name\+asc&q(%5B|\[)s(%5D|\])(%5B|\[)(%5D|\])=email\+desc/
235
+ should match(/people\?q(%5B|\[)s(%5D|\])(%5B|\[)(%5D|\])=name\+asc&q(%5B|\[)s(%5D|\])(%5B|\[)(%5D|\])=email\+desc/
236
236
  )
237
237
  }
238
238
  end
@@ -258,7 +258,7 @@ module Ransack
258
258
  )
259
259
  }
260
260
  it {
261
- should match( /people\?q(%5B|\[)s(%5D|\])(%5B|\[)(%5D|\])=name\+asc&amp;q(%5B|\[)s(%5D|\])(%5B|\[)(%5D|\])=email\+desc/
261
+ should match(/people\?q(%5B|\[)s(%5D|\])(%5B|\[)(%5D|\])=name\+asc&amp;q(%5B|\[)s(%5D|\])(%5B|\[)(%5D|\])=email\+desc/
262
262
  )
263
263
  }
264
264
  it { should match /sort_link desc/ }
@@ -274,7 +274,7 @@ module Ransack
274
274
  )
275
275
  }
276
276
  it {
277
- should match( /people\?q(%5B|\[)s(%5D|\])(%5B|\[)(%5D|\])=name\+asc&q(%5B|\[)s(%5D|\])(%5B|\[)(%5D|\])=email\+desc/
277
+ should match(/people\?q(%5B|\[)s(%5D|\])(%5B|\[)(%5D|\])=name\+asc&q(%5B|\[)s(%5D|\])(%5B|\[)(%5D|\])=email\+desc/
278
278
  )
279
279
  }
280
280
  end
@@ -289,7 +289,7 @@ module Ransack
289
289
  )
290
290
  }
291
291
  it {
292
- should match( /people\?q(%5B|\[)s(%5D|\])(%5B|\[)(%5D|\])=name\+desc&amp;q(%5B|\[)s(%5D|\])(%5B|\[)(%5D|\])=email\+desc/
292
+ should match(/people\?q(%5B|\[)s(%5D|\])(%5B|\[)(%5D|\])=name\+desc&amp;q(%5B|\[)s(%5D|\])(%5B|\[)(%5D|\])=email\+desc/
293
293
  )
294
294
  }
295
295
  it { should match /sort_link/ }
@@ -306,7 +306,7 @@ module Ransack
306
306
  )
307
307
  }
308
308
  it {
309
- should match( /people\?q(%5B|\[)s(%5D|\])(%5B|\[)(%5D|\])=name\+desc&q(%5B|\[)s(%5D|\])(%5B|\[)(%5D|\])=email\+desc/
309
+ should match(/people\?q(%5B|\[)s(%5D|\])(%5B|\[)(%5D|\])=name\+desc&q(%5B|\[)s(%5D|\])(%5B|\[)(%5D|\])=email\+desc/
310
310
  )
311
311
  }
312
312
  end
@@ -321,7 +321,7 @@ module Ransack
321
321
  )
322
322
  }
323
323
  it {
324
- should match( /people\?q(%5B|\[)s(%5D|\])(%5B|\[)(%5D|\])=name\+desc&amp;q(%5B|\[)s(%5D|\])(%5B|\[)(%5D|\])=email\+desc/
324
+ should match(/people\?q(%5B|\[)s(%5D|\])(%5B|\[)(%5D|\])=name\+desc&amp;q(%5B|\[)s(%5D|\])(%5B|\[)(%5D|\])=email\+desc/
325
325
  )
326
326
  }
327
327
  it { should match /sort_link/ }
@@ -338,7 +338,7 @@ module Ransack
338
338
  )
339
339
  }
340
340
  it {
341
- should match( /people\?q(%5B|\[)s(%5D|\])(%5B|\[)(%5D|\])=name\+desc&q(%5B|\[)s(%5D|\])(%5B|\[)(%5D|\])=email\+desc/
341
+ should match(/people\?q(%5B|\[)s(%5D|\])(%5B|\[)(%5D|\])=name\+desc&q(%5B|\[)s(%5D|\])(%5B|\[)(%5D|\])=email\+desc/
342
342
  )
343
343
  }
344
344
  end
@@ -353,7 +353,7 @@ module Ransack
353
353
  )
354
354
  }
355
355
  it {
356
- should match( /people\?q(%5B|\[)s(%5D|\])(%5B|\[)(%5D|\])=name\+desc&amp;q(%5B|\[)s(%5D|\])(%5B|\[)(%5D|\])=email\+asc/
356
+ should match(/people\?q(%5B|\[)s(%5D|\])(%5B|\[)(%5D|\])=name\+desc&amp;q(%5B|\[)s(%5D|\])(%5B|\[)(%5D|\])=email\+asc/
357
357
  )
358
358
  }
359
359
  it { should match /sort_link/ }
@@ -370,7 +370,7 @@ module Ransack
370
370
  )
371
371
  }
372
372
  it {
373
- should match( /people\?q(%5B|\[)s(%5D|\])(%5B|\[)(%5D|\])=name\+desc&q(%5B|\[)s(%5D|\])(%5B|\[)(%5D|\])=email\+asc/
373
+ should match(/people\?q(%5B|\[)s(%5D|\])(%5B|\[)(%5D|\])=name\+desc&q(%5B|\[)s(%5D|\])(%5B|\[)(%5D|\])=email\+asc/
374
374
  )
375
375
  }
376
376
  end
@@ -385,7 +385,7 @@ module Ransack
385
385
  )
386
386
  }
387
387
  it {
388
- should match( /people\?q(%5B|\[)s(%5D|\])(%5B|\[)(%5D|\])=name\+desc&amp;q(%5B|\[)s(%5D|\])(%5B|\[)(%5D|\])=email\+desc/
388
+ should match(/people\?q(%5B|\[)s(%5D|\])(%5B|\[)(%5D|\])=name\+desc&amp;q(%5B|\[)s(%5D|\])(%5B|\[)(%5D|\])=email\+desc/
389
389
  )
390
390
  }
391
391
  it { should match /sort_link/ }
@@ -402,7 +402,7 @@ module Ransack
402
402
  )
403
403
  }
404
404
  it {
405
- should match( /people\?q(%5B|\[)s(%5D|\])(%5B|\[)(%5D|\])=name\+desc&q(%5B|\[)s(%5D|\])(%5B|\[)(%5D|\])=email\+desc/
405
+ should match(/people\?q(%5B|\[)s(%5D|\])(%5B|\[)(%5D|\])=name\+desc&q(%5B|\[)s(%5D|\])(%5B|\[)(%5D|\])=email\+desc/
406
406
  )
407
407
  }
408
408
  end
@@ -643,13 +643,13 @@ module Ransack
643
643
  before do
644
644
  Ransack.configure do |c|
645
645
  c.hide_sort_order_indicators = false
646
- c.custom_arrows = { default_arrow: "defaultarrow"}
646
+ c.custom_arrows = { default_arrow: "defaultarrow" }
647
647
  end
648
648
  end
649
649
 
650
650
  after do
651
651
  Ransack.configure do |c|
652
- c.custom_arrows = { default_arrow: nil}
652
+ c.custom_arrows = { default_arrow: nil }
653
653
  end
654
654
  end
655
655
 
@@ -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 {
@@ -80,7 +80,7 @@ module Ransack
80
80
  'a' => {
81
81
  '0' => {
82
82
  'name' => 'with_arguments',
83
- 'ransacker_args' => [1,2]
83
+ 'ransacker_args' => [1, 2]
84
84
  }
85
85
  },
86
86
  'p' => 'eq',
@@ -90,7 +90,7 @@ module Ransack
90
90
  'a' => {
91
91
  '0' => {
92
92
  'name' => 'with_arguments',
93
- 'ransacker_args' => [3,4]
93
+ 'ransacker_args' => [3, 4]
94
94
  }
95
95
  },
96
96
  'p' => 'eq',
@@ -126,7 +126,7 @@ module Ransack
126
126
  (if ActiveRecord::Base.connection.adapter_name == "PostgreSQL"
127
127
  /"people"."name" ILIKE '%\\%\\.\\_\\\\%'/
128
128
  elsif ActiveRecord::Base.connection.adapter_name == "Mysql2"
129
- /`people`.`name` LIKE '%\\\\%\\\\.\\\\_\\\\\\\\%'/
129
+ /`people`.`name` LIKE '%\\\\%.\\\\_\\\\\\\\%'/
130
130
  else
131
131
  /"people"."name" LIKE '%%._\\%'/
132
132
  end) do
@@ -145,7 +145,7 @@ module Ransack
145
145
  (if ActiveRecord::Base.connection.adapter_name == "PostgreSQL"
146
146
  /"people"."name" NOT ILIKE '%\\%\\.\\_\\\\%'/
147
147
  elsif ActiveRecord::Base.connection.adapter_name == "Mysql2"
148
- /`people`.`name` NOT LIKE '%\\\\%\\\\.\\\\_\\\\\\\\%'/
148
+ /`people`.`name` NOT LIKE '%\\\\%.\\\\_\\\\\\\\%'/
149
149
  else
150
150
  /"people"."name" NOT LIKE '%%._\\%'/
151
151
  end) do
@@ -159,6 +159,44 @@ module Ransack
159
159
  end
160
160
  end
161
161
 
162
+ describe 'i_cont' do
163
+ it_has_behavior 'wildcard escaping', :name_i_cont,
164
+ (if ActiveRecord::Base.connection.adapter_name == "PostgreSQL"
165
+ /"people"."name" ILIKE '%\\%\\.\\_\\\\%'/
166
+ elsif ActiveRecord::Base.connection.adapter_name == "Mysql2"
167
+ /LOWER\(`people`.`name`\) LIKE '%\\\\%.\\\\_\\\\\\\\%'/
168
+ else
169
+ /LOWER\("people"."name"\) LIKE '%%._\\%'/
170
+ end) do
171
+ subject { @s }
172
+ end
173
+
174
+ it 'generates a LIKE query with LOWER(column) and value surrounded by %' do
175
+ @s.name_i_cont = 'Ric'
176
+ field = "#{quote_table_name("people")}.#{quote_column_name("name")}"
177
+ expect(@s.result.to_sql).to match /[LOWER\(]?#{field}\)? I?LIKE '%ric%'/
178
+ end
179
+ end
180
+
181
+ describe 'not_i_cont' do
182
+ it_has_behavior 'wildcard escaping', :name_not_i_cont,
183
+ (if ActiveRecord::Base.connection.adapter_name == "PostgreSQL"
184
+ /"people"."name" NOT ILIKE '%\\%\\.\\_\\\\%'/
185
+ elsif ActiveRecord::Base.connection.adapter_name == "Mysql2"
186
+ /LOWER\(`people`.`name`\) NOT LIKE '%\\\\%.\\\\_\\\\\\\\%'/
187
+ else
188
+ /LOWER\("people"."name"\) NOT LIKE '%%._\\%'/
189
+ end) do
190
+ subject { @s }
191
+ end
192
+
193
+ it 'generates a NOT LIKE query with LOWER(column) and value surrounded by %' do
194
+ @s.name_not_i_cont = 'Ric'
195
+ field = "#{quote_table_name("people")}.#{quote_column_name("name")}"
196
+ expect(@s.result.to_sql).to match /[LOWER\(]?#{field}\)? NOT I?LIKE '%ric%'/
197
+ end
198
+ end
199
+
162
200
  describe 'start' do
163
201
  it 'generates a LIKE query with value followed by %' do
164
202
  @s.name_start = 'Er'
@@ -381,6 +419,20 @@ module Ransack
381
419
  end
382
420
  end
383
421
 
422
+ context "defining custom predicates" do
423
+ describe "with 'not_in' arel predicate" do
424
+ before do
425
+ Ransack.configure { |c| c.add_predicate "not_in_csv", arel_predicate: "not_in", formatter: proc { |v| v.split(",") } }
426
+ end
427
+
428
+ it 'generates a value IS NOT NULL query' do
429
+ @s.name_not_in_csv = ["a", "b"]
430
+ field = "#{quote_table_name("people")}.#{quote_column_name("name")}"
431
+ expect(@s.result.to_sql).to match /#{field} NOT IN \('a', 'b'\)/
432
+ end
433
+ end
434
+ end
435
+
384
436
  private
385
437
 
386
438
  def test_boolean_equality_for(boolean_value)