ransack 2.4.2 → 3.2.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 (107) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/cronjob.yml +6 -9
  3. data/.github/workflows/deploy.yml +35 -0
  4. data/.github/workflows/rubocop.yml +1 -1
  5. data/.github/workflows/test-deploy.yml +29 -0
  6. data/.github/workflows/test.yml +16 -40
  7. data/.nojekyll +0 -0
  8. data/CHANGELOG.md +157 -11
  9. data/CONTRIBUTING.md +8 -7
  10. data/Gemfile +2 -2
  11. data/README.md +44 -972
  12. data/docs/.gitignore +19 -0
  13. data/docs/.nojekyll +0 -0
  14. data/docs/babel.config.js +3 -0
  15. data/docs/blog/2022-03-27-ransack-3.0.0.md +20 -0
  16. data/docs/docs/getting-started/_category_.json +4 -0
  17. data/docs/docs/getting-started/advanced-mode.md +46 -0
  18. data/docs/docs/getting-started/configuration.md +47 -0
  19. data/docs/docs/getting-started/search-matches.md +67 -0
  20. data/docs/docs/getting-started/simple-mode.md +284 -0
  21. data/docs/docs/getting-started/sorting.md +79 -0
  22. data/docs/docs/getting-started/using-predicates.md +282 -0
  23. data/docs/docs/going-further/_category_.json +4 -0
  24. data/docs/docs/going-further/acts-as-taggable-on.md +114 -0
  25. data/docs/docs/going-further/associations.md +70 -0
  26. data/docs/docs/going-further/custom-predicates.md +52 -0
  27. data/docs/docs/going-further/documentation.md +43 -0
  28. data/docs/docs/going-further/exporting-to-csv.md +49 -0
  29. data/docs/docs/going-further/external-guides.md +57 -0
  30. data/docs/docs/going-further/form-customisation.md +63 -0
  31. data/docs/docs/going-further/i18n.md +53 -0
  32. data/docs/docs/going-further/merging-searches.md +41 -0
  33. data/docs/docs/going-further/other-notes.md +428 -0
  34. data/docs/docs/going-further/polymorphic-search.md +40 -0
  35. data/docs/docs/going-further/ransackers.md +331 -0
  36. data/docs/docs/going-further/release_process.md +36 -0
  37. data/docs/docs/going-further/saving-queries.md +82 -0
  38. data/docs/docs/going-further/searching-postgres.md +57 -0
  39. data/docs/docs/going-further/wiki-contributors.md +82 -0
  40. data/docs/docs/intro.md +99 -0
  41. data/docs/docusaurus.config.js +120 -0
  42. data/docs/package.json +38 -0
  43. data/docs/sidebars.js +31 -0
  44. data/docs/src/components/HomepageFeatures/index.js +64 -0
  45. data/docs/src/components/HomepageFeatures/styles.module.css +11 -0
  46. data/docs/src/css/custom.css +39 -0
  47. data/docs/src/pages/index.module.css +23 -0
  48. data/docs/src/pages/markdown-page.md +7 -0
  49. data/docs/static/.nojekyll +0 -0
  50. data/docs/static/img/docusaurus.png +0 -0
  51. data/docs/static/img/favicon.ico +0 -0
  52. data/docs/static/img/logo.svg +1 -0
  53. data/docs/static/img/tutorial/docsVersionDropdown.png +0 -0
  54. data/docs/static/img/tutorial/localeDropdown.png +0 -0
  55. data/docs/static/img/undraw_docusaurus_mountain.svg +171 -0
  56. data/docs/static/img/undraw_docusaurus_react.svg +170 -0
  57. data/docs/static/img/undraw_docusaurus_tree.svg +40 -0
  58. data/docs/yarn.lock +8436 -0
  59. data/lib/polyamorous/activerecord_6.1_ruby_2/join_association.rb +0 -4
  60. data/lib/polyamorous/activerecord_6.1_ruby_2/join_dependency.rb +0 -1
  61. data/lib/polyamorous/activerecord_6.1_ruby_2/reflection.rb +11 -1
  62. data/lib/polyamorous/activerecord_7.1_ruby_2/join_association.rb +1 -0
  63. data/lib/polyamorous/activerecord_7.1_ruby_2/join_dependency.rb +1 -0
  64. data/lib/polyamorous/activerecord_7.1_ruby_2/reflection.rb +1 -0
  65. data/lib/polyamorous.rb +1 -0
  66. data/lib/ransack/adapters/active_record/base.rb +1 -3
  67. data/lib/ransack/adapters/active_record/context.rb +24 -51
  68. data/lib/ransack/adapters/active_record/ransack/nodes/condition.rb +2 -9
  69. data/lib/ransack/configuration.rb +16 -2
  70. data/lib/ransack/constants.rb +0 -3
  71. data/lib/ransack/helpers/form_helper.rb +11 -3
  72. data/lib/ransack/locale/sv.yml +70 -0
  73. data/lib/ransack/nodes/sort.rb +2 -2
  74. data/lib/ransack/nodes/value.rb +1 -1
  75. data/lib/ransack/search.rb +4 -3
  76. data/lib/ransack/translate.rb +1 -1
  77. data/lib/ransack/version.rb +1 -1
  78. data/ransack.gemspec +5 -5
  79. data/spec/helpers/polyamorous_helper.rb +2 -8
  80. data/spec/polyamorous/activerecord_compatibility_spec.rb +15 -0
  81. data/spec/polyamorous/join_association_spec.rb +1 -6
  82. data/spec/polyamorous/join_dependency_spec.rb +0 -16
  83. data/spec/ransack/adapters/active_record/base_spec.rb +28 -11
  84. data/spec/ransack/configuration_spec.rb +14 -0
  85. data/spec/ransack/helpers/form_helper_spec.rb +57 -2
  86. data/spec/ransack/nodes/condition_spec.rb +13 -0
  87. data/spec/ransack/nodes/value_spec.rb +115 -0
  88. data/spec/ransack/search_spec.rb +140 -27
  89. data/spec/support/schema.rb +49 -0
  90. metadata +82 -29
  91. data/docs/release_process.md +0 -20
  92. data/lib/polyamorous/activerecord_5.2_ruby_2/join_association.rb +0 -24
  93. data/lib/polyamorous/activerecord_5.2_ruby_2/join_dependency.rb +0 -79
  94. data/lib/polyamorous/activerecord_5.2_ruby_2/reflection.rb +0 -11
  95. data/lib/polyamorous/activerecord_6.0_ruby_2/join_association.rb +0 -1
  96. data/lib/polyamorous/activerecord_6.0_ruby_2/join_dependency.rb +0 -80
  97. data/lib/polyamorous/activerecord_6.0_ruby_2/reflection.rb +0 -1
  98. /data/docs/{img → docs/going-further/img}/create_release.png +0 -0
  99. /data/{logo → docs/static/logo}/ransack-h.png +0 -0
  100. /data/{logo → docs/static/logo}/ransack-h.svg +0 -0
  101. /data/{logo → docs/static/logo}/ransack-v.png +0 -0
  102. /data/{logo → docs/static/logo}/ransack-v.svg +0 -0
  103. /data/{logo → docs/static/logo}/ransack.png +0 -0
  104. /data/{logo → docs/static/logo}/ransack.svg +0 -0
  105. /data/lib/polyamorous/{activerecord_6.2_ruby_2 → activerecord_7.0_ruby_2}/join_association.rb +0 -0
  106. /data/lib/polyamorous/{activerecord_6.2_ruby_2 → activerecord_7.0_ruby_2}/join_dependency.rb +0 -0
  107. /data/lib/polyamorous/{activerecord_6.2_ruby_2 → activerecord_7.0_ruby_2}/reflection.rb +0 -0
@@ -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 '▼'
50
64
  expect(Ransack.options[:down_arrow]).to eq '▲'
@@ -469,8 +469,7 @@ module Ransack
469
469
  it { should match /exist\=existing/ }
470
470
  end
471
471
 
472
- context 'using a real ActionController::Parameter object',
473
- if: ::ActiveRecord::VERSION::MAJOR > 3 do
472
+ context 'using a real ActionController::Parameter object' do
474
473
 
475
474
  describe 'with symbol q:, #sort_link should include search params' do
476
475
  subject { @controller.view_context.sort_link(Person.ransack, :name) }
@@ -727,6 +726,62 @@ module Ransack
727
726
  it { should match /Block label ▼/ }
728
727
  end
729
728
 
729
+ describe '#sort_link with class option' do
730
+ subject { @controller.view_context
731
+ .sort_link(
732
+ [:main_app, Person.ransack(sorts: ['name desc'])],
733
+ :name,
734
+ class: 'people', controller: 'people'
735
+ )
736
+ }
737
+ it { should match /class="sort_link desc people"/ }
738
+ it { should_not match /people\?class=people/ }
739
+ end
740
+
741
+ describe '#sort_link with class option workaround' do
742
+ it "generates a correct link and prints a deprecation" do
743
+ expect do
744
+ link = @controller.view_context
745
+ .sort_link(
746
+ [:main_app, Person.ransack(sorts: ['name desc'])],
747
+ :name,
748
+ 'name',
749
+ { controller: 'people' },
750
+ class: 'people'
751
+ )
752
+
753
+ expect(link).to match(/class="sort_link desc people"/)
754
+ expect(link).not_to match(/people\?class=people/)
755
+ end.to output(
756
+ /Passing two trailing hashes to `sort_link` is deprecated, merge the trailing hashes into a single one\. \(called at #{Regexp.escape(__FILE__)}:/
757
+ ).to_stderr
758
+ end
759
+ end
760
+
761
+ describe '#sort_link with data option' do
762
+ subject { @controller.view_context
763
+ .sort_link(
764
+ [:main_app, Person.ransack(sorts: ['name desc'])],
765
+ :name,
766
+ data: { turbo_action: :advance }, controller: 'people'
767
+ )
768
+ }
769
+ it { should match /data-turbo-action="advance"/ }
770
+ it { should_not match /people\?data%5Bturbo_action%5D=advance/ }
771
+ end
772
+
773
+ describe "#sort_link with host option" do
774
+ subject { @controller.view_context
775
+ .sort_link(
776
+ [:main_app, Person.ransack(sorts: ['name desc'])],
777
+ :name,
778
+ host: 'foo', controller: 'people'
779
+ )
780
+ }
781
+ it { should match /href="\/people\?q/ }
782
+ it { should_not match /href=".*foo/ }
783
+ end
784
+
730
785
  describe '#search_form_for with default format' do
731
786
  subject { @controller.view_context
732
787
  .search_form_for(Person.ransack) {} }
@@ -3,6 +3,19 @@ require 'spec_helper'
3
3
  module Ransack
4
4
  module Nodes
5
5
  describe Condition do
6
+ context 'bug report #1245' do
7
+ it 'preserves tuple behavior' do
8
+ ransack_hash = {
9
+ m: 'and',
10
+ g: [
11
+ { title_type_in: ['["title 1", ""]'] }
12
+ ]
13
+ }
14
+
15
+ sql = Article.ransack(ransack_hash).result.to_sql
16
+ expect(sql).to include("IN (('title 1', ''))")
17
+ end
18
+ end
6
19
 
7
20
  context 'with an alias' do
8
21
  subject {
@@ -0,0 +1,115 @@
1
+ require 'spec_helper'
2
+
3
+ module Ransack
4
+ module Nodes
5
+ describe Value do
6
+ let(:context) { Context.for(Person) }
7
+
8
+ subject do
9
+ Value.new(context, raw_value)
10
+ end
11
+
12
+ context "with a date value" do
13
+ let(:raw_value) { "2022-05-23" }
14
+
15
+ [:date].each do |type|
16
+ it "should cast #{type} correctly" do
17
+ result = subject.cast(type)
18
+
19
+ expect(result).to be_a_kind_of(Date)
20
+ expect(result).to eq(Date.parse(raw_value))
21
+ end
22
+ end
23
+ end
24
+
25
+ context "with a timestamp value" do
26
+ let(:raw_value) { "2022-05-23 10:40:02 -0400" }
27
+
28
+ [:datetime, :timestamp, :time, :timestamptz].each do |type|
29
+ it "should cast #{type} correctly" do
30
+ result = subject.cast(type)
31
+
32
+ expect(result).to be_a_kind_of(Time)
33
+ expect(result).to eq(Time.zone.parse(raw_value))
34
+ end
35
+ end
36
+ end
37
+
38
+ Constants::TRUE_VALUES.each do |value|
39
+ context "with a true boolean value (#{value})" do
40
+ let(:raw_value) { value.to_s }
41
+
42
+ it "should cast boolean correctly" do
43
+ result = subject.cast(:boolean)
44
+ expect(result).to eq(true)
45
+ end
46
+ end
47
+ end
48
+
49
+ Constants::FALSE_VALUES.each do |value|
50
+ context "with a false boolean value (#{value})" do
51
+ let(:raw_value) { value.to_s }
52
+
53
+ it "should cast boolean correctly" do
54
+ result = subject.cast(:boolean)
55
+
56
+ expect(result).to eq(false)
57
+ end
58
+ end
59
+ end
60
+
61
+ ["12", "101.5"].each do |value|
62
+ context "with an integer value (#{value})" do
63
+ let(:raw_value) { value }
64
+
65
+ it "should cast #{value} to integer correctly" do
66
+ result = subject.cast(:integer)
67
+
68
+ expect(result).to be_an(Integer)
69
+ expect(result).to eq(value.to_i)
70
+ end
71
+ end
72
+ end
73
+
74
+ ["12", "101.5"].each do |value|
75
+ context "with a float value (#{value})" do
76
+ let(:raw_value) { value }
77
+
78
+ it "should cast #{value} to float correctly" do
79
+ result = subject.cast(:float)
80
+
81
+ expect(result).to be_an(Float)
82
+ expect(result).to eq(value.to_f)
83
+ end
84
+ end
85
+ end
86
+
87
+ ["12", "101.5"].each do |value|
88
+ context "with a decimal value (#{value})" do
89
+ let(:raw_value) { value }
90
+
91
+ it "should cast #{value} to decimal correctly" do
92
+ result = subject.cast(:decimal)
93
+
94
+ expect(result).to be_a(BigDecimal)
95
+ expect(result).to eq(value.to_d)
96
+ end
97
+ end
98
+ end
99
+
100
+ ["12", "101.513"].each do |value|
101
+ context "with a money value (#{value})" do
102
+ let(:raw_value) { value }
103
+
104
+ it "should cast #{value} to money correctly" do
105
+ result = subject.cast(:money)
106
+
107
+ expect(result).to be_a(String)
108
+ expect(result).to eq(value.to_f.to_s)
109
+ end
110
+ end
111
+ end
112
+
113
+ end
114
+ end
115
+ end
@@ -20,10 +20,42 @@ module Ransack
20
20
  Search.new(Person, name_eq: 'foobar')
21
21
  end
22
22
 
23
- it 'strip leading & trailing whitespace before building' do
24
- expect_any_instance_of(Search).to receive(:build)
25
- .with({ 'name_eq' => 'foobar' })
26
- Search.new(Person, name_eq: ' foobar ')
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
27
59
  end
28
60
 
29
61
  it 'removes empty suffixed conditions before building' do
@@ -280,6 +312,29 @@ module Ransack
280
312
  expect { Search.new(Person, params) }.not_to change { params }
281
313
  end
282
314
 
315
+ context "ransackable_scope" do
316
+ around(:each) do |example|
317
+ Person.define_singleton_method(:name_eq) do |name|
318
+ self.where(name: name)
319
+ end
320
+
321
+ begin
322
+ example.run
323
+ ensure
324
+ Person.singleton_class.undef_method :name_eq
325
+ end
326
+ end
327
+
328
+ it "is prioritized over base predicates" do
329
+ allow(Person).to receive(:ransackable_scopes)
330
+ .and_return(Person.ransackable_scopes + [:name_eq])
331
+
332
+ s = Search.new(Person, name_eq: "Johny")
333
+ expect(s.instance_variable_get(:@scope_args)["name_eq"]).to eq("Johny")
334
+ expect(s.base[:name_eq]).to be_nil
335
+ end
336
+ end
337
+
283
338
  end
284
339
 
285
340
  describe '#result' do
@@ -300,8 +355,6 @@ module Ransack
300
355
  end
301
356
 
302
357
  it 'use appropriate table alias' do
303
- skip "Rails 6 regressed here, but it's fixed in 6-0-stable since https://github.com/rails/rails/commit/f9ba52477ca288e7effa5f6794ae3df3f4e982bc" if ENV["RAILS"] == "v6.0.3"
304
-
305
358
  s = Search.new(Person, {
306
359
  name_eq: "person_name_query",
307
360
  articles_title_eq: "person_article_title_query",
@@ -451,82 +504,109 @@ module Ransack
451
504
  expect(sort.dir).to eq 'asc'
452
505
  end
453
506
 
454
- it 'creates sorts based on multiple attributes/directions in array format' do
455
- @s.sorts = ['id desc', { name: 'name', dir: 'asc' }]
507
+ it 'creates sorts based on a single alias/direction' do
508
+ @s.sorts = 'daddy desc'
509
+ expect(@s.sorts.size).to eq(1)
510
+ sort = @s.sorts.first
511
+ expect(sort).to be_a Nodes::Sort
512
+ expect(sort.name).to eq 'parent_name'
513
+ expect(sort.dir).to eq 'desc'
514
+ end
515
+
516
+ it 'creates sorts based on a single alias and uppercase direction' do
517
+ @s.sorts = 'daddy DESC'
518
+ expect(@s.sorts.size).to eq(1)
519
+ sort = @s.sorts.first
520
+ expect(sort).to be_a Nodes::Sort
521
+ expect(sort.name).to eq 'parent_name'
522
+ expect(sort.dir).to eq 'desc'
523
+ end
524
+
525
+ it 'creates sorts based on a single alias and without direction' do
526
+ @s.sorts = 'daddy'
527
+ expect(@s.sorts.size).to eq(1)
528
+ sort = @s.sorts.first
529
+ expect(sort).to be_a Nodes::Sort
530
+ expect(sort.name).to eq 'parent_name'
531
+ expect(sort.dir).to eq 'asc'
532
+ end
533
+
534
+ it 'creates sorts based on attributes, alias and directions in array format' do
535
+ @s.sorts = ['id desc', { name: 'daddy', dir: 'asc' }]
456
536
  expect(@s.sorts.size).to eq(2)
457
537
  sort1, sort2 = @s.sorts
458
538
  expect(sort1).to be_a Nodes::Sort
459
539
  expect(sort1.name).to eq 'id'
460
540
  expect(sort1.dir).to eq 'desc'
461
541
  expect(sort2).to be_a Nodes::Sort
462
- expect(sort2.name).to eq 'name'
542
+ expect(sort2.name).to eq 'parent_name'
463
543
  expect(sort2.dir).to eq 'asc'
464
544
  end
465
545
 
466
- it 'creates sorts based on multiple attributes and uppercase directions in array format' do
467
- @s.sorts = ['id DESC', { name: 'name', dir: 'ASC' }]
546
+ it 'creates sorts based on attributes, alias and uppercase directions in array format' do
547
+ @s.sorts = ['id DESC', { name: 'daddy', dir: 'ASC' }]
468
548
  expect(@s.sorts.size).to eq(2)
469
549
  sort1, sort2 = @s.sorts
470
550
  expect(sort1).to be_a Nodes::Sort
471
551
  expect(sort1.name).to eq 'id'
472
552
  expect(sort1.dir).to eq 'desc'
473
553
  expect(sort2).to be_a Nodes::Sort
474
- expect(sort2.name).to eq 'name'
554
+ expect(sort2.name).to eq 'parent_name'
475
555
  expect(sort2.dir).to eq 'asc'
476
556
  end
477
557
 
478
- it 'creates sorts based on multiple attributes and different directions
558
+ it 'creates sorts based on attributes, alias and different directions
479
559
  in array format' do
480
- @s.sorts = ['id DESC', { name: 'name', dir: nil }]
560
+ @s.sorts = ['id DESC', { name: 'daddy', dir: nil }]
481
561
  expect(@s.sorts.size).to eq(2)
482
562
  sort1, sort2 = @s.sorts
483
563
  expect(sort1).to be_a Nodes::Sort
484
564
  expect(sort1.name).to eq 'id'
485
565
  expect(sort1.dir).to eq 'desc'
486
566
  expect(sort2).to be_a Nodes::Sort
487
- expect(sort2.name).to eq 'name'
567
+ expect(sort2.name).to eq 'parent_name'
488
568
  expect(sort2.dir).to eq 'asc'
489
569
  end
490
570
 
491
- it 'creates sorts based on multiple attributes/directions in hash format' do
571
+ it 'creates sorts based on attributes, alias and directions in hash format' do
492
572
  @s.sorts = {
493
573
  '0' => { name: 'id', dir: 'desc' },
494
- '1' => { name: 'name', dir: 'asc' }
574
+ '1' => { name: 'daddy', dir: 'asc' }
495
575
  }
496
576
  expect(@s.sorts.size).to eq(2)
497
577
  expect(@s.sorts).to be_all { |s| Nodes::Sort === s }
498
578
  id_sort = @s.sorts.detect { |s| s.name == 'id' }
499
- name_sort = @s.sorts.detect { |s| s.name == 'name' }
579
+ daddy_sort = @s.sorts.detect { |s| s.name == 'parent_name' }
500
580
  expect(id_sort.dir).to eq 'desc'
501
- expect(name_sort.dir).to eq 'asc'
581
+ expect(daddy_sort.dir).to eq 'asc'
502
582
  end
503
583
 
504
- it 'creates sorts based on multiple attributes and uppercase directions
584
+ it 'creates sorts based on attributes, alias and uppercase directions
505
585
  in hash format' do
506
586
  @s.sorts = {
507
587
  '0' => { name: 'id', dir: 'DESC' },
508
- '1' => { name: 'name', dir: 'ASC' }
588
+ '1' => { name: 'daddy', dir: 'ASC' }
509
589
  }
510
590
  expect(@s.sorts.size).to eq(2)
511
591
  expect(@s.sorts).to be_all { |s| Nodes::Sort === s }
512
592
  id_sort = @s.sorts.detect { |s| s.name == 'id' }
513
- name_sort = @s.sorts.detect { |s| s.name == 'name' }
593
+ daddy_sort = @s.sorts.detect { |s| s.name == 'parent_name' }
514
594
  expect(id_sort.dir).to eq 'desc'
515
- expect(name_sort.dir).to eq 'asc'
595
+ expect(daddy_sort.dir).to eq 'asc'
516
596
  end
517
597
 
518
- it 'creates sorts based on multiple attributes and different directions
598
+ it 'creates sorts based on attributes, alias and different directions
519
599
  in hash format' do
520
600
  @s.sorts = {
521
601
  '0' => { name: 'id', dir: 'DESC' },
522
- '1' => { name: 'name', dir: nil }
602
+ '1' => { name: 'daddy', dir: nil }
523
603
  }
524
604
  expect(@s.sorts.size).to eq(2)
525
605
  expect(@s.sorts).to be_all { |s| Nodes::Sort === s }
526
606
  id_sort = @s.sorts.detect { |s| s.name == 'id' }
527
- name_sort = @s.sorts.detect { |s| s.name == 'name' }
607
+ daddy_sort = @s.sorts.detect { |s| s.name == 'parent_name' }
528
608
  expect(id_sort.dir).to eq 'desc'
529
- expect(name_sort.dir).to eq 'asc'
609
+ expect(daddy_sort.dir).to eq 'asc'
530
610
  end
531
611
 
532
612
  it 'overrides existing sort' do
@@ -554,6 +634,39 @@ module Ransack
554
634
 
555
635
  Ransack.options = default
556
636
  end
637
+
638
+ it "PG's sort option with double name", if: ::ActiveRecord::Base.connection.adapter_name == "PostgreSQL" do
639
+ default = Ransack.options.clone
640
+
641
+ s = Search.new(Person, s: 'doubled_name asc')
642
+ expect(s.result.to_sql).to eq "SELECT \"people\".* FROM \"people\" ORDER BY \"people\".\"name\" || \"people\".\"name\" ASC"
643
+
644
+ Ransack.configure { |c| c.postgres_fields_sort_option = :nulls_first }
645
+ s = Search.new(Person, s: 'doubled_name asc')
646
+ expect(s.result.to_sql).to eq "SELECT \"people\".* FROM \"people\" ORDER BY \"people\".\"name\" || \"people\".\"name\" ASC NULLS FIRST"
647
+ s = Search.new(Person, s: 'doubled_name desc')
648
+ expect(s.result.to_sql).to eq "SELECT \"people\".* FROM \"people\" ORDER BY \"people\".\"name\" || \"people\".\"name\" DESC NULLS LAST"
649
+
650
+ Ransack.configure { |c| c.postgres_fields_sort_option = :nulls_last }
651
+ s = Search.new(Person, s: 'doubled_name asc')
652
+ expect(s.result.to_sql).to eq "SELECT \"people\".* FROM \"people\" ORDER BY \"people\".\"name\" || \"people\".\"name\" ASC NULLS LAST"
653
+ s = Search.new(Person, s: 'doubled_name desc')
654
+ expect(s.result.to_sql).to eq "SELECT \"people\".* FROM \"people\" ORDER BY \"people\".\"name\" || \"people\".\"name\" DESC NULLS FIRST"
655
+
656
+ Ransack.configure { |c| c.postgres_fields_sort_option = :nulls_always_first }
657
+ s = Search.new(Person, s: 'doubled_name asc')
658
+ expect(s.result.to_sql).to eq "SELECT \"people\".* FROM \"people\" ORDER BY \"people\".\"name\" || \"people\".\"name\" ASC NULLS FIRST"
659
+ s = Search.new(Person, s: 'doubled_name desc')
660
+ expect(s.result.to_sql).to eq "SELECT \"people\".* FROM \"people\" ORDER BY \"people\".\"name\" || \"people\".\"name\" DESC NULLS FIRST"
661
+
662
+ Ransack.configure { |c| c.postgres_fields_sort_option = :nulls_always_last }
663
+ s = Search.new(Person, s: 'doubled_name asc')
664
+ expect(s.result.to_sql).to eq "SELECT \"people\".* FROM \"people\" ORDER BY \"people\".\"name\" || \"people\".\"name\" ASC NULLS LAST"
665
+ s = Search.new(Person, s: 'doubled_name desc')
666
+ expect(s.result.to_sql).to eq "SELECT \"people\".* FROM \"people\" ORDER BY \"people\".\"name\" || \"people\".\"name\" DESC NULLS LAST"
667
+
668
+ Ransack.options = default
669
+ end
557
670
  end
558
671
 
559
672
  describe '#method_missing' do
@@ -138,6 +138,45 @@ class Article < ActiveRecord::Base
138
138
  alias_attribute :content, :body
139
139
 
140
140
  default_scope { where("'default_scope' = 'default_scope'") }
141
+ scope :latest_comment_cont, lambda { |msg|
142
+ join = <<-SQL
143
+ (LEFT OUTER JOIN (
144
+ SELECT
145
+ comments.*,
146
+ row_number() OVER (PARTITION BY comments.article_id ORDER BY comments.id DESC) AS rownum
147
+ FROM comments
148
+ ) AS latest_comment
149
+ ON latest_comment.article_id = article.id
150
+ AND latest_comment.rownum = 1
151
+ )
152
+ SQL
153
+ .squish
154
+
155
+ joins(join).where("latest_comment.body ILIKE ?", "%#{msg}%")
156
+ }
157
+
158
+ ransacker :title_type, formatter: lambda { |tuples|
159
+ title, type = JSON.parse(tuples)
160
+ Arel::Nodes::Grouping.new(
161
+ [
162
+ Arel::Nodes.build_quoted(title),
163
+ Arel::Nodes.build_quoted(type)
164
+ ]
165
+ )
166
+ } do |_parent|
167
+ articles = Article.arel_table
168
+ Arel::Nodes::Grouping.new(
169
+ %i[title type].map do |field|
170
+ Arel::Nodes::NamedFunction.new(
171
+ 'COALESCE',
172
+ [
173
+ Arel::Nodes::NamedFunction.new('TRIM', [articles[field]]),
174
+ Arel::Nodes.build_quoted('')
175
+ ]
176
+ )
177
+ end
178
+ )
179
+ end
141
180
  end
142
181
 
143
182
  class StoryArticle < Article
@@ -176,6 +215,11 @@ class Note < ActiveRecord::Base
176
215
  belongs_to :notable, polymorphic: true
177
216
  end
178
217
 
218
+ class Account < ActiveRecord::Base
219
+ belongs_to :agent_account, class_name: "Account"
220
+ belongs_to :trade_account, class_name: "Account"
221
+ end
222
+
179
223
  module Schema
180
224
  def self.create
181
225
  ActiveRecord::Migration.verbose = false
@@ -234,6 +278,11 @@ module Schema
234
278
  t.integer :target_person_id
235
279
  t.integer :article_id
236
280
  end
281
+
282
+ create_table :accounts, force: true do |t|
283
+ t.belongs_to :agent_account
284
+ t.belongs_to :trade_account
285
+ end
237
286
  end
238
287
 
239
288
  10.times do