ransack 2.1.1 → 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 (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|