ransack 2.4.2 → 3.2.1

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