ransack 2.3.2 → 2.5.0

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 (78) 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/.rubocop.yml +44 -0
  8. data/CHANGELOG.md +28 -1
  9. data/CONTRIBUTING.md +16 -11
  10. data/Gemfile +5 -3
  11. data/README.md +167 -30
  12. data/bug_report_templates/test-ransack-scope-and-column-same-name.rb +78 -0
  13. data/bug_report_templates/test-ransacker-arel-present-predicate.rb +71 -0
  14. data/docs/img/create_release.png +0 -0
  15. data/docs/release_process.md +17 -0
  16. data/{polyamorous/lib → lib}/polyamorous/activerecord_5.2_ruby_2/join_association.rb +4 -0
  17. data/{polyamorous/lib → lib}/polyamorous/activerecord_5.2_ruby_2/join_dependency.rb +0 -0
  18. data/lib/polyamorous/activerecord_5.2_ruby_2/reflection.rb +11 -0
  19. data/{polyamorous/lib → lib}/polyamorous/activerecord_6.0_ruby_2/join_association.rb +0 -1
  20. data/{polyamorous/lib → lib}/polyamorous/activerecord_6.0_ruby_2/join_dependency.rb +0 -1
  21. data/{polyamorous/lib → lib}/polyamorous/activerecord_6.0_ruby_2/reflection.rb +0 -1
  22. data/lib/polyamorous/activerecord_6.1_ruby_2/join_association.rb +74 -0
  23. data/lib/polyamorous/activerecord_6.1_ruby_2/join_dependency.rb +93 -0
  24. data/{polyamorous/lib → lib}/polyamorous/activerecord_6.1_ruby_2/reflection.rb +0 -1
  25. data/lib/polyamorous/activerecord_7.0_ruby_2/join_association.rb +1 -0
  26. data/lib/polyamorous/activerecord_7.0_ruby_2/join_dependency.rb +1 -0
  27. data/lib/polyamorous/activerecord_7.0_ruby_2/reflection.rb +1 -0
  28. data/{polyamorous/lib → lib}/polyamorous/join.rb +0 -0
  29. data/{polyamorous/lib → lib/polyamorous}/polyamorous.rb +1 -1
  30. data/{polyamorous/lib → lib}/polyamorous/swapping_reflection_class.rb +0 -0
  31. data/{polyamorous/lib → lib}/polyamorous/tree_node.rb +0 -0
  32. data/lib/polyamorous.rb +1 -0
  33. data/lib/ransack/adapters/active_record/base.rb +5 -1
  34. data/lib/ransack/adapters/active_record/context.rb +55 -13
  35. data/lib/ransack/adapters/active_record/ransack/constants.rb +1 -1
  36. data/lib/ransack/adapters/active_record/ransack/context.rb +1 -0
  37. data/lib/ransack/adapters/active_record/ransack/nodes/condition.rb +11 -3
  38. data/lib/ransack/configuration.rb +31 -1
  39. data/lib/ransack/constants.rb +2 -2
  40. data/lib/ransack/helpers/form_builder.rb +3 -3
  41. data/lib/ransack/helpers.rb +1 -1
  42. data/lib/ransack/locale/sv.yml +70 -0
  43. data/lib/ransack/nodes/attribute.rb +1 -1
  44. data/lib/ransack/nodes/condition.rb +0 -2
  45. data/lib/ransack/nodes/grouping.rb +1 -1
  46. data/lib/ransack/nodes/sort.rb +3 -3
  47. data/lib/ransack/nodes/value.rb +1 -1
  48. data/lib/ransack/search.rb +4 -1
  49. data/lib/ransack/translate.rb +4 -4
  50. data/lib/ransack/version.rb +1 -1
  51. data/lib/ransack.rb +2 -2
  52. data/ransack.gemspec +8 -14
  53. data/spec/blueprints/articles.rb +1 -1
  54. data/spec/blueprints/comments.rb +1 -1
  55. data/spec/blueprints/notes.rb +1 -1
  56. data/spec/blueprints/tags.rb +1 -1
  57. data/spec/console.rb +5 -5
  58. data/spec/helpers/ransack_helper.rb +1 -1
  59. data/spec/{ransack → polyamorous}/join_association_spec.rb +8 -1
  60. data/spec/{ransack → polyamorous}/join_dependency_spec.rb +0 -0
  61. data/spec/{ransack → polyamorous}/join_spec.rb +0 -0
  62. data/spec/ransack/adapters/active_record/base_spec.rb +26 -15
  63. data/spec/ransack/adapters/active_record/context_spec.rb +19 -18
  64. data/spec/ransack/configuration_spec.rb +24 -0
  65. data/spec/ransack/helpers/form_helper_spec.rb +16 -16
  66. data/spec/ransack/nodes/condition_spec.rb +13 -0
  67. data/spec/ransack/nodes/grouping_spec.rb +2 -2
  68. data/spec/ransack/predicate_spec.rb +1 -1
  69. data/spec/ransack/search_spec.rb +215 -30
  70. data/spec/spec_helper.rb +7 -5
  71. data/spec/support/schema.rb +28 -2
  72. metadata +45 -47
  73. data/.travis.yml +0 -47
  74. data/polyamorous/lib/polyamorous/activerecord_5.2_ruby_2/reflection.rb +0 -12
  75. data/polyamorous/lib/polyamorous/activerecord_6.1_ruby_2/join_association.rb +0 -2
  76. data/polyamorous/lib/polyamorous/activerecord_6.1_ruby_2/join_dependency.rb +0 -2
  77. data/polyamorous/lib/polyamorous/version.rb +0 -3
  78. data/polyamorous/polyamorous.gemspec +0 -27
@@ -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
@@ -234,12 +338,14 @@ module Ransack
234
338
  parent_name_eq: "parent_name_query",
235
339
  parent_articles_title_eq: 'parents_article_title_query'
236
340
  }).result
341
+
237
342
  real_query = remove_quotes_and_backticks(s.to_sql)
238
343
 
239
344
  expect(real_query)
240
- .to match(%r{LEFT OUTER JOIN articles ON (\('default_scope' = 'default_scope'\) AND )?articles.person_id = people.id})
345
+ .to match(%r{LEFT OUTER JOIN articles ON (\('default_scope' = 'default_scope'\) AND )?articles.person_id = people.id})
241
346
  expect(real_query)
242
- .to match(%r{LEFT OUTER JOIN articles articles_people ON (\('default_scope' = 'default_scope'\) AND )?articles_people.person_id = parents_people.id})
347
+ .to match(%r{LEFT OUTER JOIN articles articles_people ON (\('default_scope' = 'default_scope'\) AND )?articles_people.person_id = parents_people.id})
348
+
243
349
  expect(real_query)
244
350
  .to include "people.name = 'person_name_query'"
245
351
  expect(real_query)
@@ -282,6 +388,7 @@ module Ransack
282
388
  s = Search.new(Note, notable_of_Person_type_name_eq: 'Ernie').result
283
389
  expect(s).to be_an ActiveRecord::Relation
284
390
  expect(s.to_sql).to match /#{people_name_field} = 'Ernie'/
391
+ expect(s.to_sql).to match /#{notable_type_field} = 'Person'/
285
392
  end
286
393
 
287
394
  it 'evaluates nested conditions' do
@@ -320,11 +427,8 @@ module Ransack
320
427
  { m: 'or', comments_body_cont: 'e', articles_comments_body_cont: 'e' }
321
428
  ]
322
429
  )
323
- if ActiveRecord::VERSION::MAJOR == 3
324
- all_or_load, uniq_or_distinct = :all, :uniq
325
- else
326
- all_or_load, uniq_or_distinct = :load, :distinct
327
- end
430
+
431
+ all_or_load, uniq_or_distinct = :load, :distinct
328
432
  expect(s.result.send(all_or_load).size)
329
433
  .to eq(9000)
330
434
  expect(s.result(distinct: true).size)
@@ -377,88 +481,169 @@ module Ransack
377
481
  expect(sort.dir).to eq 'asc'
378
482
  end
379
483
 
380
- it 'creates sorts based on multiple attributes/directions in array format' do
381
- @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' }]
382
513
  expect(@s.sorts.size).to eq(2)
383
514
  sort1, sort2 = @s.sorts
384
515
  expect(sort1).to be_a Nodes::Sort
385
516
  expect(sort1.name).to eq 'id'
386
517
  expect(sort1.dir).to eq 'desc'
387
518
  expect(sort2).to be_a Nodes::Sort
388
- expect(sort2.name).to eq 'name'
519
+ expect(sort2.name).to eq 'parent_name'
389
520
  expect(sort2.dir).to eq 'asc'
390
521
  end
391
522
 
392
- it 'creates sorts based on multiple attributes and uppercase directions in array format' do
393
- @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' }]
394
525
  expect(@s.sorts.size).to eq(2)
395
526
  sort1, sort2 = @s.sorts
396
527
  expect(sort1).to be_a Nodes::Sort
397
528
  expect(sort1.name).to eq 'id'
398
529
  expect(sort1.dir).to eq 'desc'
399
530
  expect(sort2).to be_a Nodes::Sort
400
- expect(sort2.name).to eq 'name'
531
+ expect(sort2.name).to eq 'parent_name'
401
532
  expect(sort2.dir).to eq 'asc'
402
533
  end
403
534
 
404
- it 'creates sorts based on multiple attributes and different directions
535
+ it 'creates sorts based on attributes, alias and different directions
405
536
  in array format' do
406
- @s.sorts = ['id DESC', { name: 'name', dir: nil }]
537
+ @s.sorts = ['id DESC', { name: 'daddy', dir: nil }]
407
538
  expect(@s.sorts.size).to eq(2)
408
539
  sort1, sort2 = @s.sorts
409
540
  expect(sort1).to be_a Nodes::Sort
410
541
  expect(sort1.name).to eq 'id'
411
542
  expect(sort1.dir).to eq 'desc'
412
543
  expect(sort2).to be_a Nodes::Sort
413
- expect(sort2.name).to eq 'name'
544
+ expect(sort2.name).to eq 'parent_name'
414
545
  expect(sort2.dir).to eq 'asc'
415
546
  end
416
547
 
417
- 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
418
549
  @s.sorts = {
419
550
  '0' => { name: 'id', dir: 'desc' },
420
- '1' => { name: 'name', dir: 'asc' }
551
+ '1' => { name: 'daddy', dir: 'asc' }
421
552
  }
422
553
  expect(@s.sorts.size).to eq(2)
423
554
  expect(@s.sorts).to be_all { |s| Nodes::Sort === s }
424
555
  id_sort = @s.sorts.detect { |s| s.name == 'id' }
425
- name_sort = @s.sorts.detect { |s| s.name == 'name' }
556
+ daddy_sort = @s.sorts.detect { |s| s.name == 'parent_name' }
426
557
  expect(id_sort.dir).to eq 'desc'
427
- expect(name_sort.dir).to eq 'asc'
558
+ expect(daddy_sort.dir).to eq 'asc'
428
559
  end
429
560
 
430
- it 'creates sorts based on multiple attributes and uppercase directions
561
+ it 'creates sorts based on attributes, alias and uppercase directions
431
562
  in hash format' do
432
563
  @s.sorts = {
433
564
  '0' => { name: 'id', dir: 'DESC' },
434
- '1' => { name: 'name', dir: 'ASC' }
565
+ '1' => { name: 'daddy', dir: 'ASC' }
435
566
  }
436
567
  expect(@s.sorts.size).to eq(2)
437
568
  expect(@s.sorts).to be_all { |s| Nodes::Sort === s }
438
569
  id_sort = @s.sorts.detect { |s| s.name == 'id' }
439
- name_sort = @s.sorts.detect { |s| s.name == 'name' }
570
+ daddy_sort = @s.sorts.detect { |s| s.name == 'parent_name' }
440
571
  expect(id_sort.dir).to eq 'desc'
441
- expect(name_sort.dir).to eq 'asc'
572
+ expect(daddy_sort.dir).to eq 'asc'
442
573
  end
443
574
 
444
- it 'creates sorts based on multiple attributes and different directions
575
+ it 'creates sorts based on attributes, alias and different directions
445
576
  in hash format' do
446
577
  @s.sorts = {
447
578
  '0' => { name: 'id', dir: 'DESC' },
448
- '1' => { name: 'name', dir: nil }
579
+ '1' => { name: 'daddy', dir: nil }
449
580
  }
450
581
  expect(@s.sorts.size).to eq(2)
451
582
  expect(@s.sorts).to be_all { |s| Nodes::Sort === s }
452
583
  id_sort = @s.sorts.detect { |s| s.name == 'id' }
453
- name_sort = @s.sorts.detect { |s| s.name == 'name' }
584
+ daddy_sort = @s.sorts.detect { |s| s.name == 'parent_name' }
454
585
  expect(id_sort.dir).to eq 'desc'
455
- expect(name_sort.dir).to eq 'asc'
586
+ expect(daddy_sort.dir).to eq 'asc'
456
587
  end
457
588
 
458
589
  it 'overrides existing sort' do
459
590
  @s.sorts = 'id asc'
460
591
  expect(@s.result.first.id).to eq 1
461
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
462
647
  end
463
648
 
464
649
  describe '#method_missing' do
data/spec/spec_helper.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  require 'machinist/active_record'
2
+ require 'polyamorous/polyamorous'
2
3
  require 'sham'
3
4
  require 'faker'
4
5
  require 'ransack'
@@ -16,16 +17,17 @@ I18n.load_path += Dir[File.join(File.dirname(__FILE__), 'support', '*.yml')]
16
17
  Dir[File.expand_path('../{helpers,support,blueprints}/*.rb', __FILE__)]
17
18
  .each { |f| require f }
18
19
 
20
+ Faker::Config.random = Random.new(0)
19
21
  Sham.define do
20
22
  name { Faker::Name.name }
21
23
  title { Faker::Lorem.sentence }
22
24
  body { Faker::Lorem.paragraph }
23
25
  salary { |index| 30000 + (index * 1000) }
24
- tag_name { Faker::Lorem.words(3).join(' ') }
25
- note { Faker::Lorem.words(7).join(' ') }
26
- only_admin { Faker::Lorem.words(3).join(' ') }
27
- only_search { Faker::Lorem.words(3).join(' ') }
28
- 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(' ') }
29
31
  notable_id { |id| id }
30
32
  end
31
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'
@@ -85,7 +87,6 @@ class Person < ActiveRecord::Base
85
87
  )
86
88
  end
87
89
 
88
-
89
90
  ransacker :sql_literal_id do
90
91
  Arel.sql('people.id')
91
92
  end
@@ -108,7 +109,6 @@ class Person < ActiveRecord::Base
108
109
  Arel.sql(query)
109
110
  end
110
111
 
111
-
112
112
  def self.ransackable_attributes(auth_object = nil)
113
113
  if auth_object == :admin
114
114
  super - ['only_sort']
@@ -138,6 +138,29 @@ class Article < ActiveRecord::Base
138
138
  alias_attribute :content, :body
139
139
 
140
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
141
164
  end
142
165
 
143
166
  class StoryArticle < Article
@@ -164,6 +187,8 @@ end
164
187
  class Comment < ActiveRecord::Base
165
188
  belongs_to :article
166
189
  belongs_to :person
190
+
191
+ default_scope { where(disabled: false) }
167
192
  end
168
193
 
169
194
  class Tag < ActiveRecord::Base
@@ -209,6 +234,7 @@ module Schema
209
234
  t.integer :article_id
210
235
  t.integer :person_id
211
236
  t.text :body
237
+ t.boolean :disabled, default: false
212
238
  end
213
239
 
214
240
  create_table :tags, force: true do |t|