ransack 1.8.4 → 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 (184) hide show
  1. checksums.yaml +5 -5
  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/deploy.yml +35 -0
  6. data/.github/workflows/rubocop.yml +20 -0
  7. data/.github/workflows/test-deploy.yml +29 -0
  8. data/.github/workflows/test.yml +130 -0
  9. data/.gitignore +3 -0
  10. data/{lib/ransack/adapters/mongoid/3.2/.gitkeep → .nojekyll} +0 -0
  11. data/.rubocop.yml +44 -0
  12. data/CHANGELOG.md +352 -0
  13. data/CONTRIBUTING.md +25 -13
  14. data/Gemfile +26 -27
  15. data/README.md +65 -815
  16. data/Rakefile +1 -22
  17. data/bug_report_templates/test-ransack-scope-and-column-same-name.rb +78 -0
  18. data/bug_report_templates/test-ransacker-arel-present-predicate.rb +71 -0
  19. data/docs/.gitignore +19 -0
  20. data/docs/.nojekyll +0 -0
  21. data/docs/babel.config.js +3 -0
  22. data/docs/blog/2022-03-27-ransack-3.0.0.md +20 -0
  23. data/docs/docs/getting-started/_category_.json +4 -0
  24. data/docs/docs/getting-started/advanced-mode.md +46 -0
  25. data/docs/docs/getting-started/configuration.md +47 -0
  26. data/docs/docs/getting-started/search-matches.md +67 -0
  27. data/docs/docs/getting-started/simple-mode.md +284 -0
  28. data/docs/docs/getting-started/sorting.md +79 -0
  29. data/docs/docs/getting-started/using-predicates.md +282 -0
  30. data/docs/docs/going-further/_category_.json +4 -0
  31. data/docs/docs/going-further/acts-as-taggable-on.md +114 -0
  32. data/docs/docs/going-further/associations.md +70 -0
  33. data/docs/docs/going-further/custom-predicates.md +52 -0
  34. data/docs/docs/going-further/documentation.md +43 -0
  35. data/docs/docs/going-further/exporting-to-csv.md +49 -0
  36. data/docs/docs/going-further/external-guides.md +57 -0
  37. data/docs/docs/going-further/form-customisation.md +63 -0
  38. data/docs/docs/going-further/i18n.md +53 -0
  39. data/docs/docs/going-further/img/create_release.png +0 -0
  40. data/docs/docs/going-further/merging-searches.md +41 -0
  41. data/docs/docs/going-further/other-notes.md +428 -0
  42. data/docs/docs/going-further/polymorphic-search.md +40 -0
  43. data/docs/docs/going-further/ransackers.md +331 -0
  44. data/docs/docs/going-further/release_process.md +36 -0
  45. data/docs/docs/going-further/saving-queries.md +82 -0
  46. data/docs/docs/going-further/searching-postgres.md +57 -0
  47. data/docs/docs/going-further/wiki-contributors.md +82 -0
  48. data/docs/docs/intro.md +99 -0
  49. data/docs/docusaurus.config.js +120 -0
  50. data/docs/package.json +38 -0
  51. data/docs/sidebars.js +31 -0
  52. data/docs/src/components/HomepageFeatures/index.js +64 -0
  53. data/docs/src/components/HomepageFeatures/styles.module.css +11 -0
  54. data/docs/src/css/custom.css +39 -0
  55. data/docs/src/pages/index.module.css +23 -0
  56. data/docs/src/pages/markdown-page.md +7 -0
  57. data/docs/static/.nojekyll +0 -0
  58. data/docs/static/img/docusaurus.png +0 -0
  59. data/docs/static/img/favicon.ico +0 -0
  60. data/docs/static/img/logo.svg +1 -0
  61. data/docs/static/img/tutorial/docsVersionDropdown.png +0 -0
  62. data/docs/static/img/tutorial/localeDropdown.png +0 -0
  63. data/docs/static/img/undraw_docusaurus_mountain.svg +171 -0
  64. data/docs/static/img/undraw_docusaurus_react.svg +170 -0
  65. data/docs/static/img/undraw_docusaurus_tree.svg +40 -0
  66. data/docs/static/logo/ransack-h.png +0 -0
  67. data/docs/static/logo/ransack-h.svg +34 -0
  68. data/docs/static/logo/ransack-v.png +0 -0
  69. data/docs/static/logo/ransack-v.svg +34 -0
  70. data/docs/static/logo/ransack.png +0 -0
  71. data/docs/static/logo/ransack.svg +21 -0
  72. data/docs/yarn.lock +8436 -0
  73. data/lib/polyamorous/activerecord_6.1_ruby_2/join_association.rb +70 -0
  74. data/lib/polyamorous/activerecord_6.1_ruby_2/join_dependency.rb +92 -0
  75. data/lib/polyamorous/activerecord_6.1_ruby_2/reflection.rb +11 -0
  76. data/lib/polyamorous/activerecord_7.0_ruby_2/join_association.rb +1 -0
  77. data/lib/polyamorous/activerecord_7.0_ruby_2/join_dependency.rb +1 -0
  78. data/lib/polyamorous/activerecord_7.0_ruby_2/reflection.rb +1 -0
  79. data/lib/polyamorous/activerecord_7.1_ruby_2/join_association.rb +1 -0
  80. data/lib/polyamorous/activerecord_7.1_ruby_2/join_dependency.rb +1 -0
  81. data/lib/polyamorous/activerecord_7.1_ruby_2/reflection.rb +1 -0
  82. data/lib/polyamorous/join.rb +70 -0
  83. data/lib/polyamorous/polyamorous.rb +24 -0
  84. data/lib/polyamorous/swapping_reflection_class.rb +11 -0
  85. data/lib/polyamorous/tree_node.rb +7 -0
  86. data/lib/polyamorous.rb +1 -0
  87. data/lib/ransack/adapters/active_record/base.rb +14 -3
  88. data/lib/ransack/adapters/active_record/context.rb +140 -196
  89. data/lib/ransack/adapters/active_record/ransack/constants.rb +19 -4
  90. data/lib/ransack/adapters/active_record/ransack/context.rb +9 -19
  91. data/lib/ransack/adapters/active_record/ransack/nodes/condition.rb +7 -7
  92. data/lib/ransack/adapters/active_record/ransack/translate.rb +1 -5
  93. data/lib/ransack/adapters/active_record/ransack/visitor.rb +23 -0
  94. data/lib/ransack/adapters/active_record.rb +0 -9
  95. data/lib/ransack/adapters.rb +2 -0
  96. data/lib/ransack/configuration.rb +52 -2
  97. data/lib/ransack/constants.rb +1 -5
  98. data/lib/ransack/context.rb +29 -24
  99. data/lib/ransack/helpers/form_builder.rb +12 -6
  100. data/lib/ransack/helpers/form_helper.rb +11 -3
  101. data/lib/ransack/helpers.rb +1 -1
  102. data/lib/ransack/locale/ar.yml +70 -0
  103. data/lib/ransack/locale/az.yml +70 -0
  104. data/lib/ransack/locale/bg.yml +70 -0
  105. data/lib/ransack/locale/ca.yml +70 -0
  106. data/lib/ransack/locale/el.yml +70 -0
  107. data/lib/ransack/locale/es.yml +22 -22
  108. data/lib/ransack/locale/fa.yml +70 -0
  109. data/lib/ransack/locale/fi.yml +71 -0
  110. data/lib/ransack/locale/nl.yml +4 -4
  111. data/lib/ransack/locale/ru.yml +70 -0
  112. data/lib/ransack/locale/sk.yml +70 -0
  113. data/lib/ransack/locale/sv.yml +70 -0
  114. data/lib/ransack/locale/tr.yml +70 -0
  115. data/lib/ransack/locale/zh-CN.yml +12 -12
  116. data/lib/ransack/nodes/attribute.rb +2 -2
  117. data/lib/ransack/nodes/condition.rb +7 -1
  118. data/lib/ransack/nodes/grouping.rb +3 -8
  119. data/lib/ransack/nodes/sort.rb +3 -3
  120. data/lib/ransack/nodes/value.rb +3 -3
  121. data/lib/ransack/predicate.rb +13 -20
  122. data/lib/ransack/search.rb +7 -4
  123. data/lib/ransack/translate.rb +115 -115
  124. data/lib/ransack/version.rb +1 -1
  125. data/lib/ransack/visitor.rb +1 -12
  126. data/lib/ransack.rb +7 -5
  127. data/ransack.gemspec +9 -25
  128. data/spec/blueprints/articles.rb +1 -1
  129. data/spec/blueprints/comments.rb +1 -1
  130. data/spec/blueprints/notes.rb +1 -1
  131. data/spec/blueprints/tags.rb +1 -1
  132. data/spec/console.rb +5 -5
  133. data/spec/helpers/polyamorous_helper.rb +13 -0
  134. data/spec/helpers/ransack_helper.rb +1 -1
  135. data/spec/polyamorous/activerecord_compatibility_spec.rb +15 -0
  136. data/spec/polyamorous/join_association_spec.rb +30 -0
  137. data/spec/polyamorous/join_dependency_spec.rb +81 -0
  138. data/spec/polyamorous/join_spec.rb +19 -0
  139. data/spec/ransack/adapters/active_record/base_spec.rb +105 -11
  140. data/spec/ransack/adapters/active_record/context_spec.rb +63 -24
  141. data/spec/ransack/configuration_spec.rb +24 -0
  142. data/spec/ransack/helpers/form_builder_spec.rb +3 -15
  143. data/spec/ransack/helpers/form_helper_spec.rb +135 -168
  144. data/spec/ransack/nodes/condition_spec.rb +13 -0
  145. data/spec/ransack/nodes/grouping_spec.rb +2 -2
  146. data/spec/ransack/nodes/value_spec.rb +115 -0
  147. data/spec/ransack/predicate_spec.rb +54 -2
  148. data/spec/ransack/search_spec.rb +266 -36
  149. data/spec/spec_helper.rb +14 -5
  150. data/spec/support/schema.rb +99 -21
  151. metadata +117 -187
  152. data/.travis.yml +0 -86
  153. data/lib/ransack/adapters/active_record/3.0/compat.rb +0 -179
  154. data/lib/ransack/adapters/active_record/3.0/context.rb +0 -203
  155. data/lib/ransack/adapters/active_record/3.1/context.rb +0 -212
  156. data/lib/ransack/adapters/active_record/3.2/context.rb +0 -44
  157. data/lib/ransack/adapters/active_record/compat.rb +0 -14
  158. data/lib/ransack/adapters/mongoid/attributes/attribute.rb +0 -37
  159. data/lib/ransack/adapters/mongoid/attributes/order_predications.rb +0 -17
  160. data/lib/ransack/adapters/mongoid/attributes/predications.rb +0 -141
  161. data/lib/ransack/adapters/mongoid/base.rb +0 -134
  162. data/lib/ransack/adapters/mongoid/context.rb +0 -212
  163. data/lib/ransack/adapters/mongoid/inquiry_hash.rb +0 -23
  164. data/lib/ransack/adapters/mongoid/ransack/constants.rb +0 -88
  165. data/lib/ransack/adapters/mongoid/ransack/context.rb +0 -60
  166. data/lib/ransack/adapters/mongoid/ransack/nodes/condition.rb +0 -27
  167. data/lib/ransack/adapters/mongoid/ransack/translate.rb +0 -13
  168. data/lib/ransack/adapters/mongoid/ransack/visitor.rb +0 -24
  169. data/lib/ransack/adapters/mongoid/table.rb +0 -35
  170. data/lib/ransack/adapters/mongoid.rb +0 -15
  171. data/spec/mongoid/adapters/mongoid/base_spec.rb +0 -314
  172. data/spec/mongoid/adapters/mongoid/context_spec.rb +0 -56
  173. data/spec/mongoid/configuration_spec.rb +0 -162
  174. data/spec/mongoid/dependencies_spec.rb +0 -8
  175. data/spec/mongoid/helpers/ransack_helper.rb +0 -11
  176. data/spec/mongoid/nodes/condition_spec.rb +0 -49
  177. data/spec/mongoid/nodes/grouping_spec.rb +0 -13
  178. data/spec/mongoid/predicate_spec.rb +0 -155
  179. data/spec/mongoid/search_spec.rb +0 -445
  180. data/spec/mongoid/support/mongoid.yml +0 -11
  181. data/spec/mongoid/support/schema.rb +0 -135
  182. data/spec/mongoid/translate_spec.rb +0 -14
  183. data/spec/mongoid_spec_helper.rb +0 -63
  184. data/spec/ransack/dependencies_spec.rb +0 -12
@@ -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
@@ -211,6 +312,29 @@ module Ransack
211
312
  expect { Search.new(Person, params) }.not_to change { params }
212
313
  end
213
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
+
214
338
  end
215
339
 
216
340
  describe '#result' do
@@ -220,6 +344,9 @@ module Ransack
220
344
  let(:children_people_name_field) {
221
345
  "#{quote_table_name("children_people")}.#{quote_column_name("name")}"
222
346
  }
347
+ let(:notable_type_field) {
348
+ "#{quote_table_name("notes")}.#{quote_column_name("notable_type")}"
349
+ }
223
350
  it 'evaluates conditions contextually' do
224
351
  s = Search.new(Person, children_name_eq: 'Ernie')
225
352
  expect(s.result).to be_an ActiveRecord::Relation
@@ -227,13 +354,32 @@ module Ransack
227
354
  children_people_name_field} = 'Ernie'/
228
355
  end
229
356
 
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',
236
- if: ::ActiveRecord::VERSION::STRING.first(3) == '4.0' do
357
+ it 'use appropriate table alias' do
358
+ s = Search.new(Person, {
359
+ name_eq: "person_name_query",
360
+ articles_title_eq: "person_article_title_query",
361
+ parent_name_eq: "parent_name_query",
362
+ parent_articles_title_eq: 'parents_article_title_query'
363
+ }).result
364
+
365
+ real_query = remove_quotes_and_backticks(s.to_sql)
366
+
367
+ expect(real_query)
368
+ .to match(%r{LEFT OUTER JOIN articles ON (\('default_scope' = 'default_scope'\) AND )?articles.person_id = people.id})
369
+ expect(real_query)
370
+ .to match(%r{LEFT OUTER JOIN articles articles_people ON (\('default_scope' = 'default_scope'\) AND )?articles_people.person_id = parents_people.id})
371
+
372
+ expect(real_query)
373
+ .to include "people.name = 'person_name_query'"
374
+ expect(real_query)
375
+ .to include "articles.title = 'person_article_title_query'"
376
+ expect(real_query)
377
+ .to include "parents_people.name = 'parent_name_query'"
378
+ expect(real_query)
379
+ .to include "articles_people.title = 'parents_article_title_query'"
380
+ end
381
+
382
+ it 'evaluates conditions for multiple `belongs_to` associations to the same table contextually' do
237
383
  s = Search.new(
238
384
  Recommendation,
239
385
  person_name_eq: 'Ernie',
@@ -248,7 +394,7 @@ module Ransack
248
394
  ON target_people_recommendations.id = recommendations.target_person_id
249
395
  LEFT OUTER JOIN people parents_people
250
396
  ON parents_people.id = target_people_recommendations.parent_id
251
- WHERE ((people.name = 'Ernie' AND parents_people.name = 'Test'))
397
+ WHERE (people.name = 'Ernie' AND parents_people.name = 'Test')
252
398
  SQL
253
399
  .squish
254
400
  expect(real_query).to eq expected_query
@@ -265,6 +411,7 @@ module Ransack
265
411
  s = Search.new(Note, notable_of_Person_type_name_eq: 'Ernie').result
266
412
  expect(s).to be_an ActiveRecord::Relation
267
413
  expect(s.to_sql).to match /#{people_name_field} = 'Ernie'/
414
+ expect(s.to_sql).to match /#{notable_type_field} = 'Person'/
268
415
  end
269
416
 
270
417
  it 'evaluates nested conditions' do
@@ -303,11 +450,8 @@ module Ransack
303
450
  { m: 'or', comments_body_cont: 'e', articles_comments_body_cont: 'e' }
304
451
  ]
305
452
  )
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
453
+
454
+ all_or_load, uniq_or_distinct = :load, :distinct
311
455
  expect(s.result.send(all_or_load).size)
312
456
  .to eq(9000)
313
457
  expect(s.result(distinct: true).size)
@@ -316,6 +460,11 @@ module Ransack
316
460
  .to eq s.result(distinct: true).send(all_or_load)
317
461
  end
318
462
 
463
+ it 'evaluates joins with belongs_to join' do
464
+ s = Person.joins(:parent).ransack(parent_name_eq: 'Ernie').result(distinct: true)
465
+ expect(s).to be_an ActiveRecord::Relation
466
+ end
467
+
319
468
  private
320
469
 
321
470
  def remove_quotes_and_backticks(str)
@@ -355,88 +504,169 @@ module Ransack
355
504
  expect(sort.dir).to eq 'asc'
356
505
  end
357
506
 
358
- it 'creates sorts based on multiple attributes/directions in array format' do
359
- @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' }]
360
536
  expect(@s.sorts.size).to eq(2)
361
537
  sort1, sort2 = @s.sorts
362
538
  expect(sort1).to be_a Nodes::Sort
363
539
  expect(sort1.name).to eq 'id'
364
540
  expect(sort1.dir).to eq 'desc'
365
541
  expect(sort2).to be_a Nodes::Sort
366
- expect(sort2.name).to eq 'name'
542
+ expect(sort2.name).to eq 'parent_name'
367
543
  expect(sort2.dir).to eq 'asc'
368
544
  end
369
545
 
370
- it 'creates sorts based on multiple attributes and uppercase directions in array format' do
371
- @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' }]
372
548
  expect(@s.sorts.size).to eq(2)
373
549
  sort1, sort2 = @s.sorts
374
550
  expect(sort1).to be_a Nodes::Sort
375
551
  expect(sort1.name).to eq 'id'
376
552
  expect(sort1.dir).to eq 'desc'
377
553
  expect(sort2).to be_a Nodes::Sort
378
- expect(sort2.name).to eq 'name'
554
+ expect(sort2.name).to eq 'parent_name'
379
555
  expect(sort2.dir).to eq 'asc'
380
556
  end
381
557
 
382
- it 'creates sorts based on multiple attributes and different directions
558
+ it 'creates sorts based on attributes, alias and different directions
383
559
  in array format' do
384
- @s.sorts = ['id DESC', { name: 'name', dir: nil }]
560
+ @s.sorts = ['id DESC', { name: 'daddy', dir: nil }]
385
561
  expect(@s.sorts.size).to eq(2)
386
562
  sort1, sort2 = @s.sorts
387
563
  expect(sort1).to be_a Nodes::Sort
388
564
  expect(sort1.name).to eq 'id'
389
565
  expect(sort1.dir).to eq 'desc'
390
566
  expect(sort2).to be_a Nodes::Sort
391
- expect(sort2.name).to eq 'name'
567
+ expect(sort2.name).to eq 'parent_name'
392
568
  expect(sort2.dir).to eq 'asc'
393
569
  end
394
570
 
395
- 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
396
572
  @s.sorts = {
397
573
  '0' => { name: 'id', dir: 'desc' },
398
- '1' => { name: 'name', dir: 'asc' }
574
+ '1' => { name: 'daddy', dir: 'asc' }
399
575
  }
400
576
  expect(@s.sorts.size).to eq(2)
401
577
  expect(@s.sorts).to be_all { |s| Nodes::Sort === s }
402
578
  id_sort = @s.sorts.detect { |s| s.name == 'id' }
403
- name_sort = @s.sorts.detect { |s| s.name == 'name' }
579
+ daddy_sort = @s.sorts.detect { |s| s.name == 'parent_name' }
404
580
  expect(id_sort.dir).to eq 'desc'
405
- expect(name_sort.dir).to eq 'asc'
581
+ expect(daddy_sort.dir).to eq 'asc'
406
582
  end
407
583
 
408
- it 'creates sorts based on multiple attributes and uppercase directions
584
+ it 'creates sorts based on attributes, alias and uppercase directions
409
585
  in hash format' do
410
586
  @s.sorts = {
411
587
  '0' => { name: 'id', dir: 'DESC' },
412
- '1' => { name: 'name', dir: 'ASC' }
588
+ '1' => { name: 'daddy', dir: 'ASC' }
413
589
  }
414
590
  expect(@s.sorts.size).to eq(2)
415
591
  expect(@s.sorts).to be_all { |s| Nodes::Sort === s }
416
592
  id_sort = @s.sorts.detect { |s| s.name == 'id' }
417
- name_sort = @s.sorts.detect { |s| s.name == 'name' }
593
+ daddy_sort = @s.sorts.detect { |s| s.name == 'parent_name' }
418
594
  expect(id_sort.dir).to eq 'desc'
419
- expect(name_sort.dir).to eq 'asc'
595
+ expect(daddy_sort.dir).to eq 'asc'
420
596
  end
421
597
 
422
- it 'creates sorts based on multiple attributes and different directions
598
+ it 'creates sorts based on attributes, alias and different directions
423
599
  in hash format' do
424
600
  @s.sorts = {
425
601
  '0' => { name: 'id', dir: 'DESC' },
426
- '1' => { name: 'name', dir: nil }
602
+ '1' => { name: 'daddy', dir: nil }
427
603
  }
428
604
  expect(@s.sorts.size).to eq(2)
429
605
  expect(@s.sorts).to be_all { |s| Nodes::Sort === s }
430
606
  id_sort = @s.sorts.detect { |s| s.name == 'id' }
431
- name_sort = @s.sorts.detect { |s| s.name == 'name' }
607
+ daddy_sort = @s.sorts.detect { |s| s.name == 'parent_name' }
432
608
  expect(id_sort.dir).to eq 'desc'
433
- expect(name_sort.dir).to eq 'asc'
609
+ expect(daddy_sort.dir).to eq 'asc'
434
610
  end
435
611
 
436
612
  it 'overrides existing sort' do
437
613
  @s.sorts = 'id asc'
438
614
  expect(@s.result.first.id).to eq 1
439
615
  end
616
+
617
+ it "PG's sort option", if: ::ActiveRecord::Base.connection.adapter_name == "PostgreSQL" do
618
+ default = Ransack.options.clone
619
+
620
+ s = Search.new(Person, s: 'name asc')
621
+ expect(s.result.to_sql).to eq "SELECT \"people\".* FROM \"people\" ORDER BY \"people\".\"name\" ASC"
622
+
623
+ Ransack.configure { |c| c.postgres_fields_sort_option = :nulls_first }
624
+ s = Search.new(Person, s: 'name asc')
625
+ expect(s.result.to_sql).to eq "SELECT \"people\".* FROM \"people\" ORDER BY \"people\".\"name\" ASC NULLS FIRST"
626
+ s = Search.new(Person, s: 'name desc')
627
+ expect(s.result.to_sql).to eq "SELECT \"people\".* FROM \"people\" ORDER BY \"people\".\"name\" DESC NULLS LAST"
628
+
629
+ Ransack.configure { |c| c.postgres_fields_sort_option = :nulls_last }
630
+ s = Search.new(Person, s: 'name asc')
631
+ expect(s.result.to_sql).to eq "SELECT \"people\".* FROM \"people\" ORDER BY \"people\".\"name\" ASC NULLS LAST"
632
+ s = Search.new(Person, s: 'name desc')
633
+ expect(s.result.to_sql).to eq "SELECT \"people\".* FROM \"people\" ORDER BY \"people\".\"name\" DESC NULLS FIRST"
634
+
635
+ Ransack.options = default
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
440
670
  end
441
671
 
442
672
  describe '#method_missing' do
data/spec/spec_helper.rb CHANGED
@@ -1,9 +1,15 @@
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'
9
+ require 'simplecov'
10
+ require 'byebug'
6
11
 
12
+ SimpleCov.start
7
13
  I18n.enforce_available_locales = false
8
14
  Time.zone = 'Eastern Time (US & Canada)'
9
15
  I18n.load_path += Dir[File.join(File.dirname(__FILE__), 'support', '*.yml')]
@@ -11,16 +17,17 @@ I18n.load_path += Dir[File.join(File.dirname(__FILE__), 'support', '*.yml')]
11
17
  Dir[File.expand_path('../{helpers,support,blueprints}/*.rb', __FILE__)]
12
18
  .each { |f| require f }
13
19
 
20
+ Faker::Config.random = Random.new(0)
14
21
  Sham.define do
15
22
  name { Faker::Name.name }
16
23
  title { Faker::Lorem.sentence }
17
24
  body { Faker::Lorem.paragraph }
18
25
  salary { |index| 30000 + (index * 1000) }
19
- tag_name { Faker::Lorem.words(3).join(' ') }
20
- note { Faker::Lorem.words(7).join(' ') }
21
- only_admin { Faker::Lorem.words(3).join(' ') }
22
- only_search { Faker::Lorem.words(3).join(' ') }
23
- 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(' ') }
24
31
  notable_id { |id| id }
25
32
  end
26
33
 
@@ -35,12 +42,14 @@ RSpec.configure do |config|
35
42
  line = '=' * message.length
36
43
  puts line, message, line
37
44
  Schema.create
45
+ SubDB::Schema.create
38
46
  end
39
47
 
40
48
  config.before(:all) { Sham.reset(:before_all) }
41
49
  config.before(:each) { Sham.reset(:before_each) }
42
50
 
43
51
  config.include RansackHelper
52
+ config.include PolyamorousHelper
44
53
  end
45
54
 
46
55
  RSpec::Matchers.define :be_like do |expected|