ransack 1.7.0 → 2.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.
Files changed (127) hide show
  1. checksums.yaml +5 -5
  2. data/.github/FUNDING.yml +3 -0
  3. data/.github/SECURITY.md +12 -0
  4. data/.github/workflows/test.yml +120 -0
  5. data/.gitignore +3 -0
  6. data/CHANGELOG.md +463 -27
  7. data/CONTRIBUTING.md +52 -22
  8. data/Gemfile +24 -24
  9. data/README.md +453 -126
  10. data/Rakefile +6 -25
  11. data/lib/polyamorous/activerecord_5.2_ruby_2/join_association.rb +24 -0
  12. data/lib/polyamorous/activerecord_5.2_ruby_2/join_dependency.rb +79 -0
  13. data/lib/polyamorous/activerecord_5.2_ruby_2/reflection.rb +11 -0
  14. data/lib/polyamorous/activerecord_6.0_ruby_2/join_association.rb +1 -0
  15. data/lib/polyamorous/activerecord_6.0_ruby_2/join_dependency.rb +80 -0
  16. data/lib/polyamorous/activerecord_6.0_ruby_2/reflection.rb +1 -0
  17. data/lib/polyamorous/activerecord_6.1_ruby_2/join_association.rb +74 -0
  18. data/lib/polyamorous/activerecord_6.1_ruby_2/join_dependency.rb +93 -0
  19. data/lib/polyamorous/activerecord_6.1_ruby_2/reflection.rb +1 -0
  20. data/lib/polyamorous/join.rb +70 -0
  21. data/lib/polyamorous/polyamorous.rb +24 -0
  22. data/lib/polyamorous/swapping_reflection_class.rb +11 -0
  23. data/lib/polyamorous/tree_node.rb +7 -0
  24. data/lib/ransack/adapters/active_record/base.rb +27 -2
  25. data/lib/ransack/adapters/active_record/context.rb +213 -139
  26. data/lib/ransack/adapters/active_record/ransack/constants.rb +70 -55
  27. data/lib/ransack/adapters/active_record/ransack/context.rb +10 -18
  28. data/lib/ransack/adapters/active_record/ransack/nodes/condition.rb +42 -32
  29. data/lib/ransack/adapters/active_record/ransack/translate.rb +1 -5
  30. data/lib/ransack/adapters/active_record/ransack/visitor.rb +23 -0
  31. data/lib/ransack/adapters/active_record.rb +11 -10
  32. data/lib/ransack/adapters.rb +45 -23
  33. data/lib/ransack/configuration.rb +107 -4
  34. data/lib/ransack/constants.rb +13 -26
  35. data/lib/ransack/context.rb +45 -33
  36. data/lib/ransack/helpers/form_builder.rb +21 -12
  37. data/lib/ransack/helpers/form_helper.rb +75 -70
  38. data/lib/ransack/locale/ar.yml +70 -0
  39. data/lib/ransack/locale/az.yml +70 -0
  40. data/lib/ransack/locale/bg.yml +70 -0
  41. data/lib/ransack/locale/ca.yml +70 -0
  42. data/lib/ransack/locale/da.yml +70 -0
  43. data/lib/ransack/locale/el.yml +70 -0
  44. data/lib/ransack/locale/es.yml +22 -22
  45. data/lib/ransack/locale/fa.yml +70 -0
  46. data/lib/ransack/locale/fi.yml +71 -0
  47. data/lib/ransack/locale/id.yml +70 -0
  48. data/lib/ransack/locale/it.yml +70 -0
  49. data/lib/ransack/locale/ja.yml +70 -0
  50. data/lib/ransack/locale/nl.yml +4 -4
  51. data/lib/ransack/locale/pt-BR.yml +70 -0
  52. data/lib/ransack/locale/ru.yml +70 -0
  53. data/lib/ransack/locale/sk.yml +70 -0
  54. data/lib/ransack/locale/tr.yml +70 -0
  55. data/lib/ransack/locale/{zh.yml → zh-CN.yml} +13 -13
  56. data/lib/ransack/locale/zh-TW.yml +70 -0
  57. data/lib/ransack/nodes/attribute.rb +5 -2
  58. data/lib/ransack/nodes/bindable.rb +18 -6
  59. data/lib/ransack/nodes/condition.rb +85 -28
  60. data/lib/ransack/nodes/grouping.rb +17 -11
  61. data/lib/ransack/nodes/sort.rb +9 -5
  62. data/lib/ransack/nodes/value.rb +74 -68
  63. data/lib/ransack/nodes.rb +1 -1
  64. data/lib/ransack/predicate.rb +17 -20
  65. data/lib/ransack/search.rb +17 -8
  66. data/lib/ransack/translate.rb +115 -115
  67. data/lib/ransack/version.rb +1 -1
  68. data/lib/ransack/visitor.rb +1 -12
  69. data/lib/ransack.rb +9 -9
  70. data/logo/ransack-h.png +0 -0
  71. data/logo/ransack-h.svg +34 -0
  72. data/logo/ransack-v.png +0 -0
  73. data/logo/ransack-v.svg +34 -0
  74. data/logo/ransack.png +0 -0
  75. data/logo/ransack.svg +21 -0
  76. data/ransack.gemspec +7 -24
  77. data/spec/console.rb +4 -0
  78. data/spec/helpers/polyamorous_helper.rb +19 -0
  79. data/spec/polyamorous/join_association_spec.rb +35 -0
  80. data/spec/polyamorous/join_dependency_spec.rb +97 -0
  81. data/spec/polyamorous/join_spec.rb +19 -0
  82. data/spec/ransack/adapters/active_record/base_spec.rb +370 -75
  83. data/spec/ransack/adapters/active_record/context_spec.rb +72 -34
  84. data/spec/ransack/configuration_spec.rb +97 -14
  85. data/spec/ransack/helpers/form_builder_spec.rb +2 -11
  86. data/spec/ransack/helpers/form_helper_spec.rb +481 -113
  87. data/spec/ransack/nodes/condition_spec.rb +24 -0
  88. data/spec/ransack/nodes/grouping_spec.rb +56 -0
  89. data/spec/ransack/predicate_spec.rb +79 -5
  90. data/spec/ransack/search_spec.rb +207 -81
  91. data/spec/spec_helper.rb +8 -0
  92. data/spec/support/schema.rb +100 -42
  93. metadata +57 -184
  94. data/.travis.yml +0 -69
  95. data/lib/ransack/adapters/active_record/3.0/compat.rb +0 -179
  96. data/lib/ransack/adapters/active_record/3.0/context.rb +0 -201
  97. data/lib/ransack/adapters/active_record/3.1/context.rb +0 -215
  98. data/lib/ransack/adapters/active_record/3.2/context.rb +0 -44
  99. data/lib/ransack/adapters/active_record/compat.rb +0 -14
  100. data/lib/ransack/adapters/mongoid/3.2/.gitkeep +0 -0
  101. data/lib/ransack/adapters/mongoid/attributes/attribute.rb +0 -37
  102. data/lib/ransack/adapters/mongoid/attributes/order_predications.rb +0 -17
  103. data/lib/ransack/adapters/mongoid/attributes/predications.rb +0 -141
  104. data/lib/ransack/adapters/mongoid/base.rb +0 -130
  105. data/lib/ransack/adapters/mongoid/context.rb +0 -208
  106. data/lib/ransack/adapters/mongoid/inquiry_hash.rb +0 -23
  107. data/lib/ransack/adapters/mongoid/ransack/constants.rb +0 -88
  108. data/lib/ransack/adapters/mongoid/ransack/context.rb +0 -60
  109. data/lib/ransack/adapters/mongoid/ransack/nodes/condition.rb +0 -27
  110. data/lib/ransack/adapters/mongoid/ransack/translate.rb +0 -13
  111. data/lib/ransack/adapters/mongoid/ransack/visitor.rb +0 -24
  112. data/lib/ransack/adapters/mongoid/table.rb +0 -35
  113. data/lib/ransack/adapters/mongoid.rb +0 -13
  114. data/spec/mongoid/adapters/mongoid/base_spec.rb +0 -276
  115. data/spec/mongoid/adapters/mongoid/context_spec.rb +0 -56
  116. data/spec/mongoid/configuration_spec.rb +0 -102
  117. data/spec/mongoid/dependencies_spec.rb +0 -8
  118. data/spec/mongoid/helpers/ransack_helper.rb +0 -11
  119. data/spec/mongoid/nodes/condition_spec.rb +0 -34
  120. data/spec/mongoid/nodes/grouping_spec.rb +0 -13
  121. data/spec/mongoid/predicate_spec.rb +0 -155
  122. data/spec/mongoid/search_spec.rb +0 -446
  123. data/spec/mongoid/support/mongoid.yml +0 -6
  124. data/spec/mongoid/support/schema.rb +0 -128
  125. data/spec/mongoid/translate_spec.rb +0 -14
  126. data/spec/mongoid_spec_helper.rb +0 -59
  127. data/spec/ransack/dependencies_spec.rb +0 -12
@@ -18,73 +18,240 @@ module Ransack
18
18
  expect(subject.object).to be_an ::ActiveRecord::Relation
19
19
  end
20
20
 
21
+ context "multiple database connection" do
22
+ it "does not raise error" do
23
+ expect { Person.ransack(name_cont: "test") }.not_to raise_error
24
+ expect { SubDB::OperationHistory.ransack(people_id_eq: 1) }.not_to raise_error
25
+ end
26
+ end
27
+
21
28
  context 'with scopes' do
22
29
  before do
23
- Person.stub :ransackable_scopes => [:active, :over_age, :of_age]
30
+ allow(Person)
31
+ .to receive(:ransackable_scopes)
32
+ .and_return([:active, :over_age, :of_age])
24
33
  end
25
34
 
26
- it "applies true scopes" do
35
+ it 'applies true scopes' do
27
36
  s = Person.ransack('active' => true)
28
37
  expect(s.result.to_sql).to (include 'active = 1')
29
38
  end
30
39
 
31
- it "applies stringy true scopes" do
40
+ it 'applies stringy true scopes' do
32
41
  s = Person.ransack('active' => 'true')
33
42
  expect(s.result.to_sql).to (include 'active = 1')
34
43
  end
35
44
 
36
- it "applies stringy boolean scopes with true value in an array" do
45
+ it 'applies stringy boolean scopes with true value in an array' do
37
46
  s = Person.ransack('of_age' => ['true'])
38
47
  expect(s.result.to_sql).to (include 'age >= 18')
39
48
  end
40
49
 
41
- it "applies stringy boolean scopes with false value in an array" do
50
+ it 'applies stringy boolean scopes with false value in an array' do
42
51
  s = Person.ransack('of_age' => ['false'])
43
52
  expect(s.result.to_sql).to (include 'age < 18')
44
53
  end
45
54
 
46
- it "ignores unlisted scopes" do
55
+ it 'ignores unlisted scopes' do
47
56
  s = Person.ransack('restricted' => true)
48
57
  expect(s.result.to_sql).to_not (include 'restricted')
49
58
  end
50
59
 
51
- it "ignores false scopes" do
60
+ it 'ignores false scopes' do
52
61
  s = Person.ransack('active' => false)
53
62
  expect(s.result.to_sql).not_to (include 'active')
54
63
  end
55
64
 
56
- it "ignores stringy false scopes" do
65
+ it 'ignores stringy false scopes' do
57
66
  s = Person.ransack('active' => 'false')
58
67
  expect(s.result.to_sql).to_not (include 'active')
59
68
  end
60
69
 
61
- it "passes values to scopes" do
70
+ it 'passes values to scopes' do
62
71
  s = Person.ransack('over_age' => 18)
63
72
  expect(s.result.to_sql).to (include 'age > 18')
64
73
  end
65
74
 
66
- it "chains scopes" do
75
+ it 'chains scopes' do
67
76
  s = Person.ransack('over_age' => 18, 'active' => true)
68
77
  expect(s.result.to_sql).to (include 'age > 18')
69
78
  expect(s.result.to_sql).to (include 'active = 1')
70
79
  end
80
+
81
+ context "with sanitize_custom_scope_booleans set to false" do
82
+ before(:all) do
83
+ Ransack.configure { |c| c.sanitize_custom_scope_booleans = false }
84
+ end
85
+
86
+ after(:all) do
87
+ Ransack.configure { |c| c.sanitize_custom_scope_booleans = true }
88
+ end
89
+
90
+ it 'passes true values to scopes' do
91
+ s = Person.ransack('over_age' => 1)
92
+ expect(s.result.to_sql).to (include 'age > 1')
93
+ end
94
+
95
+ it 'passes false values to scopes' do
96
+ s = Person.ransack('over_age' => 0)
97
+ expect(s.result.to_sql).to (include 'age > 0')
98
+ end
99
+ end
100
+
101
+ context "with ransackable_scopes_skip_sanitize_args enabled for scope" do
102
+ before do
103
+ allow(Person)
104
+ .to receive(:ransackable_scopes_skip_sanitize_args)
105
+ .and_return([:over_age])
106
+ end
107
+
108
+ it 'passes true values to scopes' do
109
+ s = Person.ransack('over_age' => 1)
110
+ expect(s.result.to_sql).to (include 'age > 1')
111
+ end
112
+
113
+ it 'passes false values to scopes' do
114
+ s = Person.ransack('over_age' => 0)
115
+ expect(s.result.to_sql).to (include 'age > 0')
116
+ end
117
+ end
118
+
71
119
  end
72
120
 
73
121
  it 'does not raise exception for string :params argument' do
74
122
  expect { Person.ransack('') }.to_not raise_error
75
123
  end
76
124
 
125
+ it 'raises exception if ransack! called with unknown condition' do
126
+ expect { Person.ransack!(unknown_attr_eq: 'Ernie') }.to raise_error
127
+ end
128
+
77
129
  it 'does not modify the parameters' do
78
- params = { :name_eq => '' }
130
+ params = { name_eq: '' }
79
131
  expect { Person.ransack(params) }.not_to change { params }
80
132
  end
81
133
  end
82
134
 
135
+ context 'negative conditions on HABTM associations' do
136
+ let(:medieval) { Tag.create!(name: 'Medieval') }
137
+ let(:fantasy) { Tag.create!(name: 'Fantasy') }
138
+ let(:arthur) { Article.create!(title: 'King Arthur') }
139
+ let(:marco) { Article.create!(title: 'Marco Polo') }
140
+
141
+ before do
142
+ marco.tags << medieval
143
+ arthur.tags << medieval
144
+ arthur.tags << fantasy
145
+ end
146
+
147
+ it 'removes redundant joins from top query' do
148
+ s = Article.ransack(tags_name_not_eq: "Fantasy")
149
+ sql = s.result.to_sql
150
+ expect(sql).to_not include('LEFT OUTER JOIN')
151
+ end
152
+
153
+ it 'handles != for single values' do
154
+ s = Article.ransack(tags_name_not_eq: "Fantasy")
155
+ articles = s.result.to_a
156
+ expect(articles).to include marco
157
+ expect(articles).to_not include arthur
158
+ end
159
+
160
+ it 'handles NOT IN for multiple attributes' do
161
+ s = Article.ransack(tags_name_not_in: ["Fantasy", "Scifi"])
162
+ articles = s.result.to_a
163
+
164
+ expect(articles).to include marco
165
+ expect(articles).to_not include arthur
166
+ end
167
+ end
168
+
169
+ context 'negative conditions on self-referenced associations' do
170
+ let(:pop) { Person.create!(name: 'Grandpa') }
171
+ let(:dad) { Person.create!(name: 'Father') }
172
+ let(:mom) { Person.create!(name: 'Mother') }
173
+ let(:son) { Person.create!(name: 'Grandchild') }
174
+
175
+ before do
176
+ son.parent = dad
177
+ dad.parent = pop
178
+ dad.children << son
179
+ mom.children << son
180
+ pop.children << dad
181
+ son.save! && dad.save! && mom.save! && pop.save!
182
+ end
183
+
184
+ it 'handles multiple associations and aliases' do
185
+ s = Person.ransack(
186
+ c: {
187
+ '0' => { a: ['name'], p: 'not_eq', v: ['Father'] },
188
+ '1' => {
189
+ a: ['children_name', 'parent_name'],
190
+ p: 'not_eq', v: ['Father'], m: 'or'
191
+ },
192
+ '2' => { a: ['children_salary'], p: 'eq', v: [nil] }
193
+ })
194
+ people = s.result
195
+
196
+ expect(people.to_a).to include son
197
+ expect(people.to_a).to include mom
198
+ expect(people.to_a).to_not include dad # rule '0': 'name'
199
+ expect(people.to_a).to_not include pop # rule '1': 'children_name'
200
+ end
201
+ end
202
+
203
+ describe '#ransack_alias' do
204
+ it 'translates an alias to the correct attributes' do
205
+ p = Person.create!(name: 'Meatloaf', email: 'babies@example.com')
206
+
207
+ s = Person.ransack(term_cont: 'atlo')
208
+ expect(s.result.to_a).to eq [p]
209
+
210
+ s = Person.ransack(term_cont: 'babi')
211
+ expect(s.result.to_a).to eq [p]
212
+
213
+ s = Person.ransack(term_cont: 'nomatch')
214
+ expect(s.result.to_a).to eq []
215
+ end
216
+
217
+ it 'also works with associations' do
218
+ dad = Person.create!(name: 'Birdman')
219
+ son = Person.create!(name: 'Weezy', parent: dad)
220
+
221
+ s = Person.ransack(daddy_eq: 'Birdman')
222
+ expect(s.result.to_a).to eq [son]
223
+
224
+ s = Person.ransack(daddy_eq: 'Drake')
225
+ expect(s.result.to_a).to eq []
226
+ end
227
+
228
+ it 'makes aliases available to subclasses' do
229
+ yngwie = Musician.create!(name: 'Yngwie Malmsteen')
230
+
231
+ musicians = Musician.ransack(term_cont: 'ngw').result
232
+ expect(musicians).to eq([yngwie])
233
+ end
234
+
235
+ it 'handles naming collisions gracefully' do
236
+ frank = Person.create!(name: 'Frank Stallone')
237
+
238
+ people = Person.ransack(term_cont: 'allon').result
239
+ expect(people).to eq([frank])
240
+
241
+ Class.new(Article) do
242
+ ransack_alias :term, :title
243
+ end
244
+
245
+ people = Person.ransack(term_cont: 'allon').result
246
+ expect(people).to eq([frank])
247
+ end
248
+ end
249
+
83
250
  describe '#ransacker' do
84
251
  # For infix tests
85
252
  def self.sane_adapter?
86
253
  case ::ActiveRecord::Base.connection.adapter_name
87
- when "SQLite3", "PostgreSQL"
254
+ when 'SQLite3', 'PostgreSQL'
88
255
  true
89
256
  else
90
257
  false
@@ -102,90 +269,128 @@ module Ransack
102
269
  # end
103
270
 
104
271
  it 'creates ransack attributes' do
105
- s = Person.ransack(:reversed_name_eq => 'htimS cirA')
272
+ person = Person.create!(name: 'Aric Smith')
273
+
274
+ s = Person.ransack(reversed_name_eq: 'htimS cirA')
106
275
  expect(s.result.size).to eq(1)
107
276
 
108
- expect(s.result.first).to eq Person.where(name: 'Aric Smith').first
277
+ expect(s.result.first).to eq person
109
278
  end
110
279
 
111
280
  it 'can be accessed through associations' do
112
- s = Person.ransack(:children_reversed_name_eq => 'htimS cirA')
281
+ s = Person.ransack(children_reversed_name_eq: 'htimS cirA')
113
282
  expect(s.result.to_sql).to match(
114
283
  /#{quote_table_name("children_people")}.#{
115
284
  quote_column_name("name")} = 'Aric Smith'/
116
285
  )
117
286
  end
118
287
 
119
- it 'allows an "attribute" to be an InfixOperation' do
120
- s = Person.ransack(:doubled_name_eq => 'Aric SmithAric Smith')
288
+ it 'allows an attribute to be an InfixOperation' do
289
+ s = Person.ransack(doubled_name_eq: 'Aric SmithAric Smith')
121
290
  expect(s.result.first).to eq Person.where(name: 'Aric Smith').first
122
291
  end if defined?(Arel::Nodes::InfixOperation) && sane_adapter?
123
292
 
124
- it "doesn't break #count if using InfixOperations" do
125
- s = Person.ransack(:doubled_name_eq => 'Aric SmithAric Smith')
293
+ it 'does not break #count if using InfixOperations' do
294
+ s = Person.ransack(doubled_name_eq: 'Aric SmithAric Smith')
126
295
  expect(s.result.count).to eq 1
127
296
  end if defined?(Arel::Nodes::InfixOperation) && sane_adapter?
128
297
 
129
- it "should remove empty key value pairs from the params hash" do
130
- s = Person.ransack(:children_reversed_name_eq => '')
298
+ it 'should remove empty key value pairs from the params hash' do
299
+ s = Person.ransack(children_reversed_name_eq: '')
131
300
  expect(s.result.to_sql).not_to match /LEFT OUTER JOIN/
132
301
  end
133
302
 
134
- it "should keep proper key value pairs in the params hash" do
135
- s = Person.ransack(:children_reversed_name_eq => 'Testing')
303
+ it 'should keep proper key value pairs in the params hash' do
304
+ s = Person.ransack(children_reversed_name_eq: 'Testing')
136
305
  expect(s.result.to_sql).to match /LEFT OUTER JOIN/
137
306
  end
138
307
 
139
- it "should function correctly when nil is passed in" do
308
+ it 'should function correctly when nil is passed in' do
140
309
  s = Person.ransack(nil)
141
310
  end
142
311
 
143
- it "should function correctly when a blank string is passed in" do
312
+ it 'should function correctly when a blank string is passed in' do
144
313
  s = Person.ransack('')
145
314
  end
146
315
 
147
- it "should function correctly with a multi-parameter attribute" do
316
+ it 'should function correctly with a multi-parameter attribute' do
148
317
  ::ActiveRecord::Base.default_timezone = :utc
149
318
  Time.zone = 'UTC'
150
319
 
151
320
  date = Date.current
152
321
  s = Person.ransack(
153
- { "created_at_gteq(1i)" => date.year,
154
- "created_at_gteq(2i)" => date.month,
155
- "created_at_gteq(3i)" => date.day
322
+ { 'created_at_gteq(1i)' => date.year,
323
+ 'created_at_gteq(2i)' => date.month,
324
+ 'created_at_gteq(3i)' => date.day
156
325
  }
157
326
  )
158
327
  expect(s.result.to_sql).to match />=/
159
328
  expect(s.result.to_sql).to match date.to_s
160
329
  end
161
330
 
162
- it "should function correctly when using fields with dots in them" do
163
- s = Person.ransack(:email_cont => "example.com")
331
+ it 'should function correctly when using fields with dots in them' do
332
+ s = Person.ransack(email_cont: 'example.com')
164
333
  expect(s.result.exists?).to be true
165
334
  end
166
335
 
167
- it "should function correctly when using fields with % in them" do
168
- p = Person.create!(:name => "110%-er")
169
- s = Person.ransack(:name_cont => "10%")
336
+ it 'should function correctly when using fields with % in them' do
337
+ p = Person.create!(name: '110%-er')
338
+ s = Person.ransack(name_cont: '10%')
170
339
  expect(s.result.to_a).to eq [p]
171
340
  end
172
341
 
173
- it "should function correctly when using fields with backslashes in them" do
174
- p = Person.create!(:name => "\\WINNER\\")
175
- s = Person.ransack(:name_cont => "\\WINNER\\")
342
+ it 'should function correctly when using fields with backslashes in them' do
343
+ p = Person.create!(name: "\\WINNER\\")
344
+ s = Person.ransack(name_cont: "\\WINNER\\")
176
345
  expect(s.result.to_a).to eq [p]
177
346
  end
178
347
 
179
- context "searching on an `in` predicate with a ransacker" do
180
- it "should function correctly when passing an array of ids" do
181
- s = Person.ransack(array_users_in: true)
348
+ context 'searching by underscores' do
349
+ # when escaping is supported right in LIKE expression without adding extra expressions
350
+ def self.simple_escaping?
351
+ case ::ActiveRecord::Base.connection.adapter_name
352
+ when 'Mysql2', 'PostgreSQL'
353
+ true
354
+ else
355
+ false
356
+ end
357
+ end
358
+
359
+ it 'should search correctly if matches exist' do
360
+ p = Person.create!(name: 'name_with_underscore')
361
+ s = Person.ransack(name_cont: 'name_')
362
+ expect(s.result.to_a).to eq [p]
363
+ end if simple_escaping?
364
+
365
+ it 'should return empty result if no matches' do
366
+ Person.create!(name: 'name_with_underscore')
367
+ s = Person.ransack(name_cont: 'n_')
368
+ expect(s.result.to_a).to eq []
369
+ end if simple_escaping?
370
+ end
371
+
372
+ context 'searching on an `in` predicate with a ransacker' do
373
+ it 'should function correctly when passing an array of ids' do
374
+ s = Person.ransack(array_people_ids_in: true)
182
375
  expect(s.result.count).to be > 0
376
+
377
+ s = Person.ransack(array_where_people_ids_in: [1, '2', 3])
378
+ expect(s.result.count).to be 3
379
+ expect(s.result.map(&:id)).to eq [3, 2, 1]
183
380
  end
184
381
 
185
- it "should function correctly when passing an array of strings" do
186
- Person.create!(name: Person.first.id.to_s)
187
- s = Person.ransack(array_names_in: true)
382
+ it 'should function correctly when passing an array of strings' do
383
+ a, b = Person.select(:id).order(:id).limit(2).map { |a| a.id.to_s }
384
+
385
+ Person.create!(name: a)
386
+ s = Person.ransack(array_people_names_in: true)
188
387
  expect(s.result.count).to be > 0
388
+ s = Person.ransack(array_where_people_names_in: a)
389
+ expect(s.result.count).to be 1
390
+
391
+ Person.create!(name: b)
392
+ s = Person.ransack(array_where_people_names_in: [a, b])
393
+ expect(s.result.count).to be 2
189
394
  end
190
395
 
191
396
  it 'should function correctly with an Arel SqlLiteral' do
@@ -196,49 +401,77 @@ module Ransack
196
401
  end
197
402
  end
198
403
 
199
- context "search on an `in` predicate with an array" do
200
- it "should function correctly when passing an array of ids" do
404
+ context 'search on an `in` predicate with an array' do
405
+ it 'should function correctly when passing an array of ids' do
201
406
  array = Person.all.map(&:id)
202
407
  s = Person.ransack(id_in: array)
203
408
  expect(s.result.count).to eq array.size
204
409
  end
205
410
  end
206
411
 
207
- it "should function correctly when an attribute name ends with '_start'" do
208
- p = Person.create!(:new_start => 'Bar and foo', :name => 'Xiang')
412
+ it 'should work correctly when an attribute name ends with _start' do
413
+ p = Person.create!(new_start: 'Bar and foo', name: 'Xiang')
209
414
 
210
- s = Person.ransack(:new_start_end => ' and foo')
415
+ s = Person.ransack(new_start_end: ' and foo')
211
416
  expect(s.result.to_a).to eq [p]
212
417
 
213
- s = Person.ransack(:name_or_new_start_start => 'Xia')
418
+ s = Person.ransack(name_or_new_start_start: 'Xia')
214
419
  expect(s.result.to_a).to eq [p]
215
420
 
216
- s = Person.ransack(:new_start_or_name_end => 'iang')
421
+ s = Person.ransack(new_start_or_name_end: 'iang')
217
422
  expect(s.result.to_a).to eq [p]
218
423
  end
219
424
 
220
- it "should function correctly when an attribute name ends with '_end'" do
221
- p = Person.create!(:stop_end => 'Foo and bar', :name => 'Marianne')
425
+ it 'should work correctly when an attribute name ends with _end' do
426
+ p = Person.create!(stop_end: 'Foo and bar', name: 'Marianne')
222
427
 
223
- s = Person.ransack(:stop_end_start => 'Foo and')
428
+ s = Person.ransack(stop_end_start: 'Foo and')
224
429
  expect(s.result.to_a).to eq [p]
225
430
 
226
- s = Person.ransack(:stop_end_or_name_end => 'anne')
431
+ s = Person.ransack(stop_end_or_name_end: 'anne')
227
432
  expect(s.result.to_a).to eq [p]
228
433
 
229
- s = Person.ransack(:name_or_stop_end_end => ' bar')
434
+ s = Person.ransack(name_or_stop_end_end: ' bar')
230
435
  expect(s.result.to_a).to eq [p]
231
436
  end
232
437
 
233
- it "should function correctly when an attribute name has 'and' in it" do
234
- p = Person.create!(:terms_and_conditions => true)
235
- s = Person.ransack(:terms_and_conditions_eq => true)
438
+ it 'should work correctly when an attribute name has `and` in it' do
439
+ p = Person.create!(terms_and_conditions: true)
440
+ s = Person.ransack(terms_and_conditions_eq: true)
236
441
  expect(s.result.to_a).to eq [p]
237
442
  end
238
443
 
239
- it 'allows sort by "only_sort" field' do
444
+ context 'attribute aliased column names',
445
+ if: Ransack::SUPPORTS_ATTRIBUTE_ALIAS do
446
+ it 'should be translated to original column name' do
447
+ s = Person.ransack(full_name_eq: 'Nicolas Cage')
448
+ expect(s.result.to_sql).to match(
449
+ /WHERE #{quote_table_name("people")}.#{quote_column_name("name")}/
450
+ )
451
+ end
452
+
453
+ it 'should translate on associations' do
454
+ s = Person.ransack(articles_content_cont: 'Nicolas Cage')
455
+ expect(s.result.to_sql).to match(
456
+ /#{quote_table_name("articles")}.#{
457
+ quote_column_name("body")} I?LIKE '%Nicolas Cage%'/
458
+ )
459
+ end
460
+ end
461
+
462
+ it 'sorts with different join variants' do
463
+ comments = [
464
+ Comment.create(article: Article.create(title: 'Avenger'), person: Person.create(salary: 100_000)),
465
+ Comment.create(article: Article.create(title: 'Avenge'), person: Person.create(salary: 50_000)),
466
+ ]
467
+ expect(Comment.ransack(article_title_cont: 'aven',s: 'person_salary desc').result).to eq(comments)
468
+ expect(Comment.joins(:person).ransack(s: 'persons_salarydesc', article_title_cont: 'aven').result).to eq(comments)
469
+ expect(Comment.joins(:person).ransack(article_title_cont: 'aven',s: 'persons_salary desc').result).to eq(comments)
470
+ end
471
+
472
+ it 'allows sort by `only_sort` field' do
240
473
  s = Person.ransack(
241
- "s" => { "0" => { "dir" => "asc", "name" => "only_sort" } }
474
+ 's' => { '0' => { 'dir' => 'asc', 'name' => 'only_sort' } }
242
475
  )
243
476
  expect(s.result.to_sql).to match(
244
477
  /ORDER BY #{quote_table_name("people")}.#{
@@ -246,9 +479,9 @@ module Ransack
246
479
  )
247
480
  end
248
481
 
249
- it "doesn't sort by 'only_search' field" do
482
+ it 'does not sort by `only_search` field' do
250
483
  s = Person.ransack(
251
- "s" => { "0" => { "dir" => "asc", "name" => "only_search" } }
484
+ 's' => { '0' => { 'dir' => 'asc', 'name' => 'only_search' } }
252
485
  )
253
486
  expect(s.result.to_sql).not_to match(
254
487
  /ORDER BY #{quote_table_name("people")}.#{
@@ -256,25 +489,25 @@ module Ransack
256
489
  )
257
490
  end
258
491
 
259
- it 'allows search by "only_search" field' do
260
- s = Person.ransack(:only_search_eq => 'htimS cirA')
492
+ it 'allows search by `only_search` field' do
493
+ s = Person.ransack(only_search_eq: 'htimS cirA')
261
494
  expect(s.result.to_sql).to match(
262
495
  /WHERE #{quote_table_name("people")}.#{
263
496
  quote_column_name("only_search")} = 'htimS cirA'/
264
497
  )
265
498
  end
266
499
 
267
- it "can't be searched by 'only_sort'" do
268
- s = Person.ransack(:only_sort_eq => 'htimS cirA')
500
+ it 'cannot be searched by `only_sort`' do
501
+ s = Person.ransack(only_sort_eq: 'htimS cirA')
269
502
  expect(s.result.to_sql).not_to match(
270
503
  /WHERE #{quote_table_name("people")}.#{
271
504
  quote_column_name("only_sort")} = 'htimS cirA'/
272
505
  )
273
506
  end
274
507
 
275
- it 'allows sort by "only_admin" field, if auth_object: :admin' do
508
+ it 'allows sort by `only_admin` field, if auth_object: :admin' do
276
509
  s = Person.ransack(
277
- { "s" => { "0" => { "dir" => "asc", "name" => "only_admin" } } },
510
+ { 's' => { '0' => { 'dir' => 'asc', 'name' => 'only_admin' } } },
278
511
  { auth_object: :admin }
279
512
  )
280
513
  expect(s.result.to_sql).to match(
@@ -283,9 +516,9 @@ module Ransack
283
516
  )
284
517
  end
285
518
 
286
- it "doesn't sort by 'only_admin' field, if auth_object: nil" do
519
+ it 'does not sort by `only_admin` field, if auth_object: nil' do
287
520
  s = Person.ransack(
288
- "s" => { "0" => { "dir" => "asc", "name" => "only_admin" } }
521
+ 's' => { '0' => { 'dir' => 'asc', 'name' => 'only_admin' } }
289
522
  )
290
523
  expect(s.result.to_sql).not_to match(
291
524
  /ORDER BY #{quote_table_name("people")}.#{
@@ -293,10 +526,10 @@ module Ransack
293
526
  )
294
527
  end
295
528
 
296
- it 'allows search by "only_admin" field, if auth_object: :admin' do
529
+ it 'allows search by `only_admin` field, if auth_object: :admin' do
297
530
  s = Person.ransack(
298
- { :only_admin_eq => 'htimS cirA' },
299
- { :auth_object => :admin }
531
+ { only_admin_eq: 'htimS cirA' },
532
+ { auth_object: :admin }
300
533
  )
301
534
  expect(s.result.to_sql).to match(
302
535
  /WHERE #{quote_table_name("people")}.#{
@@ -304,14 +537,15 @@ module Ransack
304
537
  )
305
538
  end
306
539
 
307
- it "can't be searched by 'only_admin', if auth_object: nil" do
308
- s = Person.ransack(:only_admin_eq => 'htimS cirA')
540
+ it 'cannot be searched by `only_admin`, if auth_object: nil' do
541
+ s = Person.ransack(only_admin_eq: 'htimS cirA')
309
542
  expect(s.result.to_sql).not_to match(
310
543
  /WHERE #{quote_table_name("people")}.#{
311
544
  quote_column_name("only_admin")} = 'htimS cirA'/
312
545
  )
313
546
  end
314
547
 
548
+
315
549
  it 'should allow passing ransacker arguments to a ransacker' do
316
550
  s = Person.ransack(
317
551
  c: [{
@@ -332,6 +566,56 @@ module Ransack
332
566
  )
333
567
  expect { s.result.first }.to_not raise_error
334
568
  end
569
+
570
+ it 'should allow sort passing arguments to a ransacker' do
571
+ s = Person.ransack(
572
+ s: {
573
+ '0' => {
574
+ name: 'with_arguments', dir: 'desc', ransacker_args: [2, 6]
575
+ }
576
+ }
577
+ )
578
+ expect(s.result.to_sql).to match(
579
+ /ORDER BY \(SELECT MAX\(articles.title\) FROM articles/
580
+ )
581
+ expect(s.result.to_sql).to match(
582
+ /WHERE articles.person_id = people.id AND LENGTH\(articles.body\)/
583
+ )
584
+ expect(s.result.to_sql).to match(
585
+ /BETWEEN 2 AND 6 GROUP BY articles.person_id \) DESC/
586
+ )
587
+ end
588
+
589
+ context 'case insensitive sorting' do
590
+ it 'allows sort by desc' do
591
+ search = Person.ransack(sorts: ['name_case_insensitive desc'])
592
+ expect(search.result.to_sql).to match /ORDER BY LOWER(.*) DESC/
593
+ end
594
+
595
+ it 'allows sort by asc' do
596
+ search = Person.ransack(sorts: ['name_case_insensitive asc'])
597
+ expect(search.result.to_sql).to match /ORDER BY LOWER(.*) ASC/
598
+ end
599
+ end
600
+
601
+ context 'regular sorting' do
602
+ it 'allows sort by desc' do
603
+ search = Person.ransack(sorts: ['name desc'])
604
+ expect(search.result.to_sql).to match /ORDER BY .* DESC/
605
+ end
606
+
607
+ it 'allows sort by asc' do
608
+ search = Person.ransack(sorts: ['name asc'])
609
+ expect(search.result.to_sql).to match /ORDER BY .* ASC/
610
+ end
611
+ end
612
+
613
+ context 'sorting by a scope' do
614
+ it 'applies the correct scope' do
615
+ search = Person.ransack(sorts: ['reverse_name asc'])
616
+ expect(search.result.to_sql).to include("ORDER BY REVERSE(name) ASC")
617
+ end
618
+ end
335
619
  end
336
620
 
337
621
  describe '#ransackable_attributes' do
@@ -341,9 +625,14 @@ module Ransack
341
625
  it { should include 'name' }
342
626
  it { should include 'reversed_name' }
343
627
  it { should include 'doubled_name' }
628
+ it { should include 'term' }
344
629
  it { should include 'only_search' }
345
630
  it { should_not include 'only_sort' }
346
631
  it { should_not include 'only_admin' }
632
+
633
+ if Ransack::SUPPORTS_ATTRIBUTE_ALIAS
634
+ it { should include 'full_name' }
635
+ end
347
636
  end
348
637
 
349
638
  context 'with auth_object :admin' do
@@ -396,6 +685,12 @@ module Ransack
396
685
  it { should eq [] }
397
686
  end
398
687
 
688
+ describe '#ransackable_scopes_skip_sanitize_args' do
689
+ subject { Person.ransackable_scopes_skip_sanitize_args }
690
+
691
+ it { should eq [] }
692
+ end
693
+
399
694
  end
400
695
  end
401
696
  end