ransack 1.7.0 → 2.4.1

Sign up to get free protection for your applications and to get access to all the features.
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