ransack 2.1.1 → 2.5.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -20,6 +20,44 @@ module Ransack
20
20
  Search.new(Person, name_eq: 'foobar')
21
21
  end
22
22
 
23
+ context 'whitespace stripping' do
24
+ context 'when whitespace_strip option is true' do
25
+ before do
26
+ Ransack.configure { |c| c.strip_whitespace = true }
27
+ end
28
+
29
+ it 'strips leading & trailing whitespace before building' do
30
+ expect_any_instance_of(Search).to receive(:build)
31
+ .with({ 'name_eq' => 'foobar' })
32
+ Search.new(Person, name_eq: ' foobar ')
33
+ end
34
+ end
35
+
36
+ context 'when whitespace_strip option is false' do
37
+ before do
38
+ Ransack.configure { |c| c.strip_whitespace = false }
39
+ end
40
+
41
+ it 'doesn\'t strip leading & trailing whitespace before building' do
42
+ expect_any_instance_of(Search).to receive(:build)
43
+ .with({ 'name_eq' => ' foobar ' })
44
+ Search.new(Person, name_eq: ' foobar ')
45
+ end
46
+ end
47
+
48
+ it 'strips leading & trailing whitespace when strip_whitespace search parameter is true' do
49
+ expect_any_instance_of(Search).to receive(:build)
50
+ .with({ 'name_eq' => 'foobar' })
51
+ Search.new(Person, { name_eq: ' foobar ' }, { strip_whitespace: true })
52
+ end
53
+
54
+ it 'doesn\'t strip leading & trailing whitespace when strip_whitespace search parameter is false' do
55
+ expect_any_instance_of(Search).to receive(:build)
56
+ .with({ 'name_eq' => ' foobar ' })
57
+ Search.new(Person, { name_eq: ' foobar ' }, { strip_whitespace: false })
58
+ end
59
+ end
60
+
23
61
  it 'removes empty suffixed conditions before building' do
24
62
  expect_any_instance_of(Search).to receive(:build).with({})
25
63
  Search.new(Person, name_eq_any: [''])
@@ -109,6 +147,43 @@ module Ransack
109
147
  expect(s.result.to_sql).to include 'published'
110
148
  end
111
149
 
150
+ # The failure/oversight in Ransack::Nodes::Condition#arel_predicate or deeper is beyond my understanding of the structures
151
+ it 'preserves (inverts) default scope and conditions for negative subqueries' do
152
+ # the positive case (published_articles_title_eq) is
153
+ # SELECT "people".* FROM "people"
154
+ # LEFT OUTER JOIN "articles" ON "articles"."person_id" = "people"."id"
155
+ # AND "articles"."published" = 't'
156
+ # AND ('default_scope' = 'default_scope')
157
+ # WHERE "articles"."title" = 'Test' ORDER BY "people"."id" DESC
158
+ #
159
+ # negative case was
160
+ # SELECT "people".* FROM "people" WHERE "people"."id" NOT IN (
161
+ # SELECT "articles"."person_id" FROM "articles"
162
+ # WHERE "articles"."person_id" = "people"."id"
163
+ # AND NOT ("articles"."title" != 'Test')
164
+ # ) ORDER BY "people"."id" DESC
165
+ #
166
+ # Should have been like
167
+ # SELECT "people".* FROM "people" WHERE "people"."id" NOT IN (
168
+ # SELECT "articles"."person_id" FROM "articles"
169
+ # WHERE "articles"."person_id" = "people"."id"
170
+ # AND "articles"."title" = 'Test' AND "articles"."published" = 't' AND ('default_scope' = 'default_scope')
171
+ # ) ORDER BY "people"."id" DESC
172
+ #
173
+ # With tenanting (eg default_scope with column reference), NOT IN should be like
174
+ # SELECT "people".* FROM "people" WHERE "people"."tenant_id" = 'tenant_id' AND "people"."id" NOT IN (
175
+ # SELECT "articles"."person_id" FROM "articles"
176
+ # WHERE "articles"."person_id" = "people"."id"
177
+ # AND "articles"."tenant_id" = 'tenant_id'
178
+ # AND "articles"."title" = 'Test' AND "articles"."published" = 't' AND ('default_scope' = 'default_scope')
179
+ # ) ORDER BY "people"."id" DESC
180
+
181
+ pending("spec should pass, but I do not know how/where to fix lib code")
182
+ s = Search.new(Person, published_articles_title_not_eq: 'Test')
183
+ expect(s.result.to_sql).to include 'default_scope'
184
+ expect(s.result.to_sql).to include 'published'
185
+ end
186
+
112
187
  it 'discards empty conditions' do
113
188
  s = Search.new(Person, children_name_eq: '')
114
189
  condition = s.base[:children_name_eq]
@@ -189,7 +264,7 @@ module Ransack
189
264
  context 'with an invalid condition' do
190
265
  subject { Search.new(Person, unknown_attr_eq: 'Ernie') }
191
266
 
192
- context 'when ignore_unknown_conditions is false' do
267
+ context 'when ignore_unknown_conditions configuration option is false' do
193
268
  before do
194
269
  Ransack.configure { |c| c.ignore_unknown_conditions = false }
195
270
  end
@@ -197,13 +272,39 @@ module Ransack
197
272
  specify { expect { subject }.to raise_error ArgumentError }
198
273
  end
199
274
 
200
- context 'when ignore_unknown_conditions is true' do
275
+ context 'when ignore_unknown_conditions configuration option is true' do
201
276
  before do
202
277
  Ransack.configure { |c| c.ignore_unknown_conditions = true }
203
278
  end
204
279
 
205
280
  specify { expect { subject }.not_to raise_error }
206
281
  end
282
+
283
+ subject(:with_ignore_unknown_conditions_false) {
284
+ Search.new(Person,
285
+ { unknown_attr_eq: 'Ernie' },
286
+ { ignore_unknown_conditions: false }
287
+ )
288
+ }
289
+
290
+ subject(:with_ignore_unknown_conditions_true) {
291
+ Search.new(Person,
292
+ { unknown_attr_eq: 'Ernie' },
293
+ { ignore_unknown_conditions: true }
294
+ )
295
+ }
296
+
297
+ context 'when ignore_unknown_conditions search parameter is absent' do
298
+ specify { expect { subject }.not_to raise_error }
299
+ end
300
+
301
+ context 'when ignore_unknown_conditions search parameter is false' do
302
+ specify { expect { with_ignore_unknown_conditions_false }.to raise_error ArgumentError }
303
+ end
304
+
305
+ context 'when ignore_unknown_conditions search parameter is true' do
306
+ specify { expect { with_ignore_unknown_conditions_true }.not_to raise_error }
307
+ end
207
308
  end
208
309
 
209
310
  it 'does not modify the parameters' do
@@ -220,6 +321,9 @@ module Ransack
220
321
  let(:children_people_name_field) {
221
322
  "#{quote_table_name("children_people")}.#{quote_column_name("name")}"
222
323
  }
324
+ let(:notable_type_field) {
325
+ "#{quote_table_name("notes")}.#{quote_column_name("notable_type")}"
326
+ }
223
327
  it 'evaluates conditions contextually' do
224
328
  s = Search.new(Person, children_name_eq: 'Ernie')
225
329
  expect(s.result).to be_an ActiveRecord::Relation
@@ -227,13 +331,32 @@ module Ransack
227
331
  children_people_name_field} = 'Ernie'/
228
332
  end
229
333
 
230
- # FIXME: Make this spec pass for Rails 4.1 / 4.2 / 5.0 and not just 4.0 by
231
- # commenting out lines 221 and 242 to run the test. Addresses issue #374.
232
- # https://github.com/activerecord-hackery/ransack/issues/374
233
- #
234
- it 'evaluates conditions for multiple `belongs_to` associations to the
235
- same table contextually' do
236
- skip "Make this spec pass for Rails >5.0"
334
+ it 'use appropriate table alias' do
335
+ s = Search.new(Person, {
336
+ name_eq: "person_name_query",
337
+ articles_title_eq: "person_article_title_query",
338
+ parent_name_eq: "parent_name_query",
339
+ parent_articles_title_eq: 'parents_article_title_query'
340
+ }).result
341
+
342
+ real_query = remove_quotes_and_backticks(s.to_sql)
343
+
344
+ expect(real_query)
345
+ .to match(%r{LEFT OUTER JOIN articles ON (\('default_scope' = 'default_scope'\) AND )?articles.person_id = people.id})
346
+ expect(real_query)
347
+ .to match(%r{LEFT OUTER JOIN articles articles_people ON (\('default_scope' = 'default_scope'\) AND )?articles_people.person_id = parents_people.id})
348
+
349
+ expect(real_query)
350
+ .to include "people.name = 'person_name_query'"
351
+ expect(real_query)
352
+ .to include "articles.title = 'person_article_title_query'"
353
+ expect(real_query)
354
+ .to include "parents_people.name = 'parent_name_query'"
355
+ expect(real_query)
356
+ .to include "articles_people.title = 'parents_article_title_query'"
357
+ end
358
+
359
+ it 'evaluates conditions for multiple `belongs_to` associations to the same table contextually' do
237
360
  s = Search.new(
238
361
  Recommendation,
239
362
  person_name_eq: 'Ernie',
@@ -248,7 +371,7 @@ module Ransack
248
371
  ON target_people_recommendations.id = recommendations.target_person_id
249
372
  LEFT OUTER JOIN people parents_people
250
373
  ON parents_people.id = target_people_recommendations.parent_id
251
- WHERE ((people.name = 'Ernie' AND parents_people.name = 'Test'))
374
+ WHERE (people.name = 'Ernie' AND parents_people.name = 'Test')
252
375
  SQL
253
376
  .squish
254
377
  expect(real_query).to eq expected_query
@@ -265,6 +388,7 @@ module Ransack
265
388
  s = Search.new(Note, notable_of_Person_type_name_eq: 'Ernie').result
266
389
  expect(s).to be_an ActiveRecord::Relation
267
390
  expect(s.to_sql).to match /#{people_name_field} = 'Ernie'/
391
+ expect(s.to_sql).to match /#{notable_type_field} = 'Person'/
268
392
  end
269
393
 
270
394
  it 'evaluates nested conditions' do
@@ -303,11 +427,8 @@ module Ransack
303
427
  { m: 'or', comments_body_cont: 'e', articles_comments_body_cont: 'e' }
304
428
  ]
305
429
  )
306
- if ActiveRecord::VERSION::MAJOR == 3
307
- all_or_load, uniq_or_distinct = :all, :uniq
308
- else
309
- all_or_load, uniq_or_distinct = :load, :distinct
310
- end
430
+
431
+ all_or_load, uniq_or_distinct = :load, :distinct
311
432
  expect(s.result.send(all_or_load).size)
312
433
  .to eq(9000)
313
434
  expect(s.result(distinct: true).size)
@@ -360,88 +481,169 @@ module Ransack
360
481
  expect(sort.dir).to eq 'asc'
361
482
  end
362
483
 
363
- it 'creates sorts based on multiple attributes/directions in array format' do
364
- @s.sorts = ['id desc', { name: 'name', dir: 'asc' }]
484
+ it 'creates sorts based on a single alias/direction' do
485
+ @s.sorts = 'daddy desc'
486
+ expect(@s.sorts.size).to eq(1)
487
+ sort = @s.sorts.first
488
+ expect(sort).to be_a Nodes::Sort
489
+ expect(sort.name).to eq 'parent_name'
490
+ expect(sort.dir).to eq 'desc'
491
+ end
492
+
493
+ it 'creates sorts based on a single alias and uppercase direction' do
494
+ @s.sorts = 'daddy DESC'
495
+ expect(@s.sorts.size).to eq(1)
496
+ sort = @s.sorts.first
497
+ expect(sort).to be_a Nodes::Sort
498
+ expect(sort.name).to eq 'parent_name'
499
+ expect(sort.dir).to eq 'desc'
500
+ end
501
+
502
+ it 'creates sorts based on a single alias and without direction' do
503
+ @s.sorts = 'daddy'
504
+ expect(@s.sorts.size).to eq(1)
505
+ sort = @s.sorts.first
506
+ expect(sort).to be_a Nodes::Sort
507
+ expect(sort.name).to eq 'parent_name'
508
+ expect(sort.dir).to eq 'asc'
509
+ end
510
+
511
+ it 'creates sorts based on attributes, alias and directions in array format' do
512
+ @s.sorts = ['id desc', { name: 'daddy', dir: 'asc' }]
365
513
  expect(@s.sorts.size).to eq(2)
366
514
  sort1, sort2 = @s.sorts
367
515
  expect(sort1).to be_a Nodes::Sort
368
516
  expect(sort1.name).to eq 'id'
369
517
  expect(sort1.dir).to eq 'desc'
370
518
  expect(sort2).to be_a Nodes::Sort
371
- expect(sort2.name).to eq 'name'
519
+ expect(sort2.name).to eq 'parent_name'
372
520
  expect(sort2.dir).to eq 'asc'
373
521
  end
374
522
 
375
- it 'creates sorts based on multiple attributes and uppercase directions in array format' do
376
- @s.sorts = ['id DESC', { name: 'name', dir: 'ASC' }]
523
+ it 'creates sorts based on attributes, alias and uppercase directions in array format' do
524
+ @s.sorts = ['id DESC', { name: 'daddy', dir: 'ASC' }]
377
525
  expect(@s.sorts.size).to eq(2)
378
526
  sort1, sort2 = @s.sorts
379
527
  expect(sort1).to be_a Nodes::Sort
380
528
  expect(sort1.name).to eq 'id'
381
529
  expect(sort1.dir).to eq 'desc'
382
530
  expect(sort2).to be_a Nodes::Sort
383
- expect(sort2.name).to eq 'name'
531
+ expect(sort2.name).to eq 'parent_name'
384
532
  expect(sort2.dir).to eq 'asc'
385
533
  end
386
534
 
387
- it 'creates sorts based on multiple attributes and different directions
535
+ it 'creates sorts based on attributes, alias and different directions
388
536
  in array format' do
389
- @s.sorts = ['id DESC', { name: 'name', dir: nil }]
537
+ @s.sorts = ['id DESC', { name: 'daddy', dir: nil }]
390
538
  expect(@s.sorts.size).to eq(2)
391
539
  sort1, sort2 = @s.sorts
392
540
  expect(sort1).to be_a Nodes::Sort
393
541
  expect(sort1.name).to eq 'id'
394
542
  expect(sort1.dir).to eq 'desc'
395
543
  expect(sort2).to be_a Nodes::Sort
396
- expect(sort2.name).to eq 'name'
544
+ expect(sort2.name).to eq 'parent_name'
397
545
  expect(sort2.dir).to eq 'asc'
398
546
  end
399
547
 
400
- it 'creates sorts based on multiple attributes/directions in hash format' do
548
+ it 'creates sorts based on attributes, alias and directions in hash format' do
401
549
  @s.sorts = {
402
550
  '0' => { name: 'id', dir: 'desc' },
403
- '1' => { name: 'name', dir: 'asc' }
551
+ '1' => { name: 'daddy', dir: 'asc' }
404
552
  }
405
553
  expect(@s.sorts.size).to eq(2)
406
554
  expect(@s.sorts).to be_all { |s| Nodes::Sort === s }
407
555
  id_sort = @s.sorts.detect { |s| s.name == 'id' }
408
- name_sort = @s.sorts.detect { |s| s.name == 'name' }
556
+ daddy_sort = @s.sorts.detect { |s| s.name == 'parent_name' }
409
557
  expect(id_sort.dir).to eq 'desc'
410
- expect(name_sort.dir).to eq 'asc'
558
+ expect(daddy_sort.dir).to eq 'asc'
411
559
  end
412
560
 
413
- it 'creates sorts based on multiple attributes and uppercase directions
561
+ it 'creates sorts based on attributes, alias and uppercase directions
414
562
  in hash format' do
415
563
  @s.sorts = {
416
564
  '0' => { name: 'id', dir: 'DESC' },
417
- '1' => { name: 'name', dir: 'ASC' }
565
+ '1' => { name: 'daddy', dir: 'ASC' }
418
566
  }
419
567
  expect(@s.sorts.size).to eq(2)
420
568
  expect(@s.sorts).to be_all { |s| Nodes::Sort === s }
421
569
  id_sort = @s.sorts.detect { |s| s.name == 'id' }
422
- name_sort = @s.sorts.detect { |s| s.name == 'name' }
570
+ daddy_sort = @s.sorts.detect { |s| s.name == 'parent_name' }
423
571
  expect(id_sort.dir).to eq 'desc'
424
- expect(name_sort.dir).to eq 'asc'
572
+ expect(daddy_sort.dir).to eq 'asc'
425
573
  end
426
574
 
427
- it 'creates sorts based on multiple attributes and different directions
575
+ it 'creates sorts based on attributes, alias and different directions
428
576
  in hash format' do
429
577
  @s.sorts = {
430
578
  '0' => { name: 'id', dir: 'DESC' },
431
- '1' => { name: 'name', dir: nil }
579
+ '1' => { name: 'daddy', dir: nil }
432
580
  }
433
581
  expect(@s.sorts.size).to eq(2)
434
582
  expect(@s.sorts).to be_all { |s| Nodes::Sort === s }
435
583
  id_sort = @s.sorts.detect { |s| s.name == 'id' }
436
- name_sort = @s.sorts.detect { |s| s.name == 'name' }
584
+ daddy_sort = @s.sorts.detect { |s| s.name == 'parent_name' }
437
585
  expect(id_sort.dir).to eq 'desc'
438
- expect(name_sort.dir).to eq 'asc'
586
+ expect(daddy_sort.dir).to eq 'asc'
439
587
  end
440
588
 
441
589
  it 'overrides existing sort' do
442
590
  @s.sorts = 'id asc'
443
591
  expect(@s.result.first.id).to eq 1
444
592
  end
593
+
594
+ it "PG's sort option", if: ::ActiveRecord::Base.connection.adapter_name == "PostgreSQL" do
595
+ default = Ransack.options.clone
596
+
597
+ s = Search.new(Person, s: 'name asc')
598
+ expect(s.result.to_sql).to eq "SELECT \"people\".* FROM \"people\" ORDER BY \"people\".\"name\" ASC"
599
+
600
+ Ransack.configure { |c| c.postgres_fields_sort_option = :nulls_first }
601
+ s = Search.new(Person, s: 'name asc')
602
+ expect(s.result.to_sql).to eq "SELECT \"people\".* FROM \"people\" ORDER BY \"people\".\"name\" ASC NULLS FIRST"
603
+ s = Search.new(Person, s: 'name desc')
604
+ expect(s.result.to_sql).to eq "SELECT \"people\".* FROM \"people\" ORDER BY \"people\".\"name\" DESC NULLS LAST"
605
+
606
+ Ransack.configure { |c| c.postgres_fields_sort_option = :nulls_last }
607
+ s = Search.new(Person, s: 'name asc')
608
+ expect(s.result.to_sql).to eq "SELECT \"people\".* FROM \"people\" ORDER BY \"people\".\"name\" ASC NULLS LAST"
609
+ s = Search.new(Person, s: 'name desc')
610
+ expect(s.result.to_sql).to eq "SELECT \"people\".* FROM \"people\" ORDER BY \"people\".\"name\" DESC NULLS FIRST"
611
+
612
+ Ransack.options = default
613
+ end
614
+
615
+ it "PG's sort option with double name", if: ::ActiveRecord::Base.connection.adapter_name == "PostgreSQL" do
616
+ default = Ransack.options.clone
617
+
618
+ s = Search.new(Person, s: 'doubled_name asc')
619
+ expect(s.result.to_sql).to eq "SELECT \"people\".* FROM \"people\" ORDER BY \"people\".\"name\" || \"people\".\"name\" ASC"
620
+
621
+ Ransack.configure { |c| c.postgres_fields_sort_option = :nulls_first }
622
+ s = Search.new(Person, s: 'doubled_name asc')
623
+ expect(s.result.to_sql).to eq "SELECT \"people\".* FROM \"people\" ORDER BY \"people\".\"name\" || \"people\".\"name\" ASC NULLS FIRST"
624
+ s = Search.new(Person, s: 'doubled_name desc')
625
+ expect(s.result.to_sql).to eq "SELECT \"people\".* FROM \"people\" ORDER BY \"people\".\"name\" || \"people\".\"name\" DESC NULLS LAST"
626
+
627
+ Ransack.configure { |c| c.postgres_fields_sort_option = :nulls_last }
628
+ s = Search.new(Person, s: 'doubled_name asc')
629
+ expect(s.result.to_sql).to eq "SELECT \"people\".* FROM \"people\" ORDER BY \"people\".\"name\" || \"people\".\"name\" ASC NULLS LAST"
630
+ s = Search.new(Person, s: 'doubled_name desc')
631
+ expect(s.result.to_sql).to eq "SELECT \"people\".* FROM \"people\" ORDER BY \"people\".\"name\" || \"people\".\"name\" DESC NULLS FIRST"
632
+
633
+ Ransack.configure { |c| c.postgres_fields_sort_option = :nulls_always_first }
634
+ s = Search.new(Person, s: 'doubled_name asc')
635
+ expect(s.result.to_sql).to eq "SELECT \"people\".* FROM \"people\" ORDER BY \"people\".\"name\" || \"people\".\"name\" ASC NULLS FIRST"
636
+ s = Search.new(Person, s: 'doubled_name desc')
637
+ expect(s.result.to_sql).to eq "SELECT \"people\".* FROM \"people\" ORDER BY \"people\".\"name\" || \"people\".\"name\" DESC NULLS FIRST"
638
+
639
+ Ransack.configure { |c| c.postgres_fields_sort_option = :nulls_always_last }
640
+ s = Search.new(Person, s: 'doubled_name asc')
641
+ expect(s.result.to_sql).to eq "SELECT \"people\".* FROM \"people\" ORDER BY \"people\".\"name\" || \"people\".\"name\" ASC NULLS LAST"
642
+ s = Search.new(Person, s: 'doubled_name desc')
643
+ expect(s.result.to_sql).to eq "SELECT \"people\".* FROM \"people\" ORDER BY \"people\".\"name\" || \"people\".\"name\" DESC NULLS LAST"
644
+
645
+ Ransack.options = default
646
+ end
445
647
  end
446
648
 
447
649
  describe '#method_missing' do
data/spec/spec_helper.rb CHANGED
@@ -1,9 +1,13 @@
1
1
  require 'machinist/active_record'
2
+ require 'polyamorous/polyamorous'
2
3
  require 'sham'
3
4
  require 'faker'
4
5
  require 'ransack'
6
+ require 'action_controller'
7
+ require 'ransack/helpers'
5
8
  require 'pry'
6
9
  require 'simplecov'
10
+ require 'byebug'
7
11
 
8
12
  SimpleCov.start
9
13
  I18n.enforce_available_locales = false
@@ -13,16 +17,17 @@ I18n.load_path += Dir[File.join(File.dirname(__FILE__), 'support', '*.yml')]
13
17
  Dir[File.expand_path('../{helpers,support,blueprints}/*.rb', __FILE__)]
14
18
  .each { |f| require f }
15
19
 
20
+ Faker::Config.random = Random.new(0)
16
21
  Sham.define do
17
22
  name { Faker::Name.name }
18
23
  title { Faker::Lorem.sentence }
19
24
  body { Faker::Lorem.paragraph }
20
25
  salary { |index| 30000 + (index * 1000) }
21
- tag_name { Faker::Lorem.words(3).join(' ') }
22
- note { Faker::Lorem.words(7).join(' ') }
23
- only_admin { Faker::Lorem.words(3).join(' ') }
24
- only_search { Faker::Lorem.words(3).join(' ') }
25
- only_sort { Faker::Lorem.words(3).join(' ') }
26
+ tag_name { Faker::Lorem.words(number: 3).join(' ') }
27
+ note { Faker::Lorem.words(number: 7).join(' ') }
28
+ only_admin { Faker::Lorem.words(number: 3).join(' ') }
29
+ only_search { Faker::Lorem.words(number: 3).join(' ') }
30
+ only_sort { Faker::Lorem.words(number: 3).join(' ') }
26
31
  notable_id { |id| id }
27
32
  end
28
33
 
@@ -6,6 +6,8 @@ when 'mysql', 'mysql2'
6
6
  ActiveRecord::Base.establish_connection(
7
7
  adapter: 'mysql2',
8
8
  database: 'ransack',
9
+ username: ENV.fetch("MYSQL_USERNAME") { "root" },
10
+ password: ENV.fetch("MYSQL_PASSWORD") { "" },
9
11
  encoding: 'utf8'
10
12
  )
11
13
  when 'pg', 'postgres', 'postgresql'
@@ -13,7 +15,9 @@ when 'pg', 'postgres', 'postgresql'
13
15
  ActiveRecord::Base.establish_connection(
14
16
  adapter: 'postgresql',
15
17
  database: 'ransack',
16
- # username: 'postgres', # Uncomment the username option if you have set one
18
+ username: ENV.fetch("DATABASE_USERNAME") { "postgres" },
19
+ password: ENV.fetch("DATABASE_PASSWORD") { "" },
20
+ host: ENV.fetch("DATABASE_HOST") { "localhost" },
17
21
  min_messages: 'warning'
18
22
  )
19
23
  else
@@ -29,6 +33,8 @@ class Person < ActiveRecord::Base
29
33
  belongs_to :parent, class_name: 'Person', foreign_key: :parent_id
30
34
  has_many :children, class_name: 'Person', foreign_key: :parent_id
31
35
  has_many :articles
36
+ has_many :story_articles
37
+
32
38
  has_many :published_articles, ->{ where(published: true) },
33
39
  class_name: "Article"
34
40
  has_many :comments
@@ -81,7 +87,6 @@ class Person < ActiveRecord::Base
81
87
  )
82
88
  end
83
89
 
84
-
85
90
  ransacker :sql_literal_id do
86
91
  Arel.sql('people.id')
87
92
  end
@@ -104,7 +109,6 @@ class Person < ActiveRecord::Base
104
109
  Arel.sql(query)
105
110
  end
106
111
 
107
-
108
112
  def self.ransackable_attributes(auth_object = nil)
109
113
  if auth_object == :admin
110
114
  super - ['only_sort']
@@ -134,6 +138,32 @@ class Article < ActiveRecord::Base
134
138
  alias_attribute :content, :body
135
139
 
136
140
  default_scope { where("'default_scope' = 'default_scope'") }
141
+
142
+ ransacker :title_type, formatter: lambda { |tuples|
143
+ title, type = JSON.parse(tuples)
144
+ Arel::Nodes::Grouping.new(
145
+ [
146
+ Arel::Nodes.build_quoted(title),
147
+ Arel::Nodes.build_quoted(type)
148
+ ]
149
+ )
150
+ } do |_parent|
151
+ articles = Article.arel_table
152
+ Arel::Nodes::Grouping.new(
153
+ %i[title type].map do |field|
154
+ Arel::Nodes::NamedFunction.new(
155
+ 'COALESCE',
156
+ [
157
+ Arel::Nodes::NamedFunction.new('TRIM', [articles[field]]),
158
+ Arel::Nodes.build_quoted('')
159
+ ]
160
+ )
161
+ end
162
+ )
163
+ end
164
+ end
165
+
166
+ class StoryArticle < Article
137
167
  end
138
168
 
139
169
  class Recommendation < ActiveRecord::Base
@@ -157,6 +187,8 @@ end
157
187
  class Comment < ActiveRecord::Base
158
188
  belongs_to :article
159
189
  belongs_to :person
190
+
191
+ default_scope { where(disabled: false) }
160
192
  end
161
193
 
162
194
  class Tag < ActiveRecord::Base
@@ -194,6 +226,7 @@ module Schema
194
226
  t.string :title
195
227
  t.text :subject_header
196
228
  t.text :body
229
+ t.string :type
197
230
  t.boolean :published, default: true
198
231
  end
199
232
 
@@ -201,6 +234,7 @@ module Schema
201
234
  t.integer :article_id
202
235
  t.integer :person_id
203
236
  t.text :body
237
+ t.boolean :disabled, default: false
204
238
  end
205
239
 
206
240
  create_table :tags, force: true do |t|