ransack 1.7.0 → 2.4.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (127) 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/test.yml +120 -0
  5. data/.gitignore +3 -0
  6. data/CHANGELOG.md +463 -27
  7. data/CONTRIBUTING.md +52 -22
  8. data/Gemfile +24 -24
  9. data/README.md +453 -126
  10. data/Rakefile +6 -25
  11. data/lib/polyamorous/activerecord_5.2_ruby_2/join_association.rb +24 -0
  12. data/lib/polyamorous/activerecord_5.2_ruby_2/join_dependency.rb +79 -0
  13. data/lib/polyamorous/activerecord_5.2_ruby_2/reflection.rb +11 -0
  14. data/lib/polyamorous/activerecord_6.0_ruby_2/join_association.rb +1 -0
  15. data/lib/polyamorous/activerecord_6.0_ruby_2/join_dependency.rb +80 -0
  16. data/lib/polyamorous/activerecord_6.0_ruby_2/reflection.rb +1 -0
  17. data/lib/polyamorous/activerecord_6.1_ruby_2/join_association.rb +74 -0
  18. data/lib/polyamorous/activerecord_6.1_ruby_2/join_dependency.rb +93 -0
  19. data/lib/polyamorous/activerecord_6.1_ruby_2/reflection.rb +1 -0
  20. data/lib/polyamorous/join.rb +70 -0
  21. data/lib/polyamorous/polyamorous.rb +24 -0
  22. data/lib/polyamorous/swapping_reflection_class.rb +11 -0
  23. data/lib/polyamorous/tree_node.rb +7 -0
  24. data/lib/ransack/adapters/active_record/base.rb +27 -2
  25. data/lib/ransack/adapters/active_record/context.rb +213 -139
  26. data/lib/ransack/adapters/active_record/ransack/constants.rb +70 -55
  27. data/lib/ransack/adapters/active_record/ransack/context.rb +10 -18
  28. data/lib/ransack/adapters/active_record/ransack/nodes/condition.rb +42 -32
  29. data/lib/ransack/adapters/active_record/ransack/translate.rb +1 -5
  30. data/lib/ransack/adapters/active_record/ransack/visitor.rb +23 -0
  31. data/lib/ransack/adapters/active_record.rb +11 -10
  32. data/lib/ransack/adapters.rb +45 -23
  33. data/lib/ransack/configuration.rb +107 -4
  34. data/lib/ransack/constants.rb +13 -26
  35. data/lib/ransack/context.rb +45 -33
  36. data/lib/ransack/helpers/form_builder.rb +21 -12
  37. data/lib/ransack/helpers/form_helper.rb +75 -70
  38. data/lib/ransack/locale/ar.yml +70 -0
  39. data/lib/ransack/locale/az.yml +70 -0
  40. data/lib/ransack/locale/bg.yml +70 -0
  41. data/lib/ransack/locale/ca.yml +70 -0
  42. data/lib/ransack/locale/da.yml +70 -0
  43. data/lib/ransack/locale/el.yml +70 -0
  44. data/lib/ransack/locale/es.yml +22 -22
  45. data/lib/ransack/locale/fa.yml +70 -0
  46. data/lib/ransack/locale/fi.yml +71 -0
  47. data/lib/ransack/locale/id.yml +70 -0
  48. data/lib/ransack/locale/it.yml +70 -0
  49. data/lib/ransack/locale/ja.yml +70 -0
  50. data/lib/ransack/locale/nl.yml +4 -4
  51. data/lib/ransack/locale/pt-BR.yml +70 -0
  52. data/lib/ransack/locale/ru.yml +70 -0
  53. data/lib/ransack/locale/sk.yml +70 -0
  54. data/lib/ransack/locale/tr.yml +70 -0
  55. data/lib/ransack/locale/{zh.yml → zh-CN.yml} +13 -13
  56. data/lib/ransack/locale/zh-TW.yml +70 -0
  57. data/lib/ransack/nodes/attribute.rb +5 -2
  58. data/lib/ransack/nodes/bindable.rb +18 -6
  59. data/lib/ransack/nodes/condition.rb +85 -28
  60. data/lib/ransack/nodes/grouping.rb +17 -11
  61. data/lib/ransack/nodes/sort.rb +9 -5
  62. data/lib/ransack/nodes/value.rb +74 -68
  63. data/lib/ransack/nodes.rb +1 -1
  64. data/lib/ransack/predicate.rb +17 -20
  65. data/lib/ransack/search.rb +17 -8
  66. data/lib/ransack/translate.rb +115 -115
  67. data/lib/ransack/version.rb +1 -1
  68. data/lib/ransack/visitor.rb +1 -12
  69. data/lib/ransack.rb +9 -9
  70. data/logo/ransack-h.png +0 -0
  71. data/logo/ransack-h.svg +34 -0
  72. data/logo/ransack-v.png +0 -0
  73. data/logo/ransack-v.svg +34 -0
  74. data/logo/ransack.png +0 -0
  75. data/logo/ransack.svg +21 -0
  76. data/ransack.gemspec +7 -24
  77. data/spec/console.rb +4 -0
  78. data/spec/helpers/polyamorous_helper.rb +19 -0
  79. data/spec/polyamorous/join_association_spec.rb +35 -0
  80. data/spec/polyamorous/join_dependency_spec.rb +97 -0
  81. data/spec/polyamorous/join_spec.rb +19 -0
  82. data/spec/ransack/adapters/active_record/base_spec.rb +370 -75
  83. data/spec/ransack/adapters/active_record/context_spec.rb +72 -34
  84. data/spec/ransack/configuration_spec.rb +97 -14
  85. data/spec/ransack/helpers/form_builder_spec.rb +2 -11
  86. data/spec/ransack/helpers/form_helper_spec.rb +481 -113
  87. data/spec/ransack/nodes/condition_spec.rb +24 -0
  88. data/spec/ransack/nodes/grouping_spec.rb +56 -0
  89. data/spec/ransack/predicate_spec.rb +79 -5
  90. data/spec/ransack/search_spec.rb +207 -81
  91. data/spec/spec_helper.rb +8 -0
  92. data/spec/support/schema.rb +100 -42
  93. metadata +57 -184
  94. data/.travis.yml +0 -69
  95. data/lib/ransack/adapters/active_record/3.0/compat.rb +0 -179
  96. data/lib/ransack/adapters/active_record/3.0/context.rb +0 -201
  97. data/lib/ransack/adapters/active_record/3.1/context.rb +0 -215
  98. data/lib/ransack/adapters/active_record/3.2/context.rb +0 -44
  99. data/lib/ransack/adapters/active_record/compat.rb +0 -14
  100. data/lib/ransack/adapters/mongoid/3.2/.gitkeep +0 -0
  101. data/lib/ransack/adapters/mongoid/attributes/attribute.rb +0 -37
  102. data/lib/ransack/adapters/mongoid/attributes/order_predications.rb +0 -17
  103. data/lib/ransack/adapters/mongoid/attributes/predications.rb +0 -141
  104. data/lib/ransack/adapters/mongoid/base.rb +0 -130
  105. data/lib/ransack/adapters/mongoid/context.rb +0 -208
  106. data/lib/ransack/adapters/mongoid/inquiry_hash.rb +0 -23
  107. data/lib/ransack/adapters/mongoid/ransack/constants.rb +0 -88
  108. data/lib/ransack/adapters/mongoid/ransack/context.rb +0 -60
  109. data/lib/ransack/adapters/mongoid/ransack/nodes/condition.rb +0 -27
  110. data/lib/ransack/adapters/mongoid/ransack/translate.rb +0 -13
  111. data/lib/ransack/adapters/mongoid/ransack/visitor.rb +0 -24
  112. data/lib/ransack/adapters/mongoid/table.rb +0 -35
  113. data/lib/ransack/adapters/mongoid.rb +0 -13
  114. data/spec/mongoid/adapters/mongoid/base_spec.rb +0 -276
  115. data/spec/mongoid/adapters/mongoid/context_spec.rb +0 -56
  116. data/spec/mongoid/configuration_spec.rb +0 -102
  117. data/spec/mongoid/dependencies_spec.rb +0 -8
  118. data/spec/mongoid/helpers/ransack_helper.rb +0 -11
  119. data/spec/mongoid/nodes/condition_spec.rb +0 -34
  120. data/spec/mongoid/nodes/grouping_spec.rb +0 -13
  121. data/spec/mongoid/predicate_spec.rb +0 -155
  122. data/spec/mongoid/search_spec.rb +0 -446
  123. data/spec/mongoid/support/mongoid.yml +0 -6
  124. data/spec/mongoid/support/schema.rb +0 -128
  125. data/spec/mongoid/translate_spec.rb +0 -14
  126. data/spec/mongoid_spec_helper.rb +0 -59
  127. data/spec/ransack/dependencies_spec.rb +0 -12
@@ -20,6 +20,12 @@ 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 ')
27
+ end
28
+
23
29
  it 'removes empty suffixed conditions before building' do
24
30
  expect_any_instance_of(Search).to receive(:build).with({})
25
31
  Search.new(Person, name_eq_any: [''])
@@ -43,16 +49,16 @@ module Ransack
43
49
 
44
50
  it 'accepts a context option' do
45
51
  shared_context = Context.for(Person)
46
- search1 = Search.new(Person, { name_eq: 'A' }, context: shared_context)
47
- search2 = Search.new(Person, { name_eq: 'B' }, context: shared_context)
48
- expect(search1.context).to be search2.context
52
+ s1 = Search.new(Person, { name_eq: 'A' }, context: shared_context)
53
+ s2 = Search.new(Person, { name_eq: 'B' }, context: shared_context)
54
+ expect(s1.context).to be s2.context
49
55
  end
50
56
  end
51
57
 
52
58
  describe '#build' do
53
59
  it 'creates conditions for top-level attributes' do
54
- search = Search.new(Person, name_eq: 'Ernie')
55
- condition = search.base[:name_eq]
60
+ s = Search.new(Person, name_eq: 'Ernie')
61
+ condition = s.base[:name_eq]
56
62
  expect(condition).to be_a Nodes::Condition
57
63
  expect(condition.predicate.name).to eq 'eq'
58
64
  expect(condition.attributes.first.name).to eq 'name'
@@ -60,8 +66,8 @@ module Ransack
60
66
  end
61
67
 
62
68
  it 'creates conditions for association attributes' do
63
- search = Search.new(Person, children_name_eq: 'Ernie')
64
- condition = search.base[:children_name_eq]
69
+ s = Search.new(Person, children_name_eq: 'Ernie')
70
+ condition = s.base[:children_name_eq]
65
71
  expect(condition).to be_a Nodes::Condition
66
72
  expect(condition.predicate.name).to eq 'eq'
67
73
  expect(condition.attributes.first.name).to eq 'children_name'
@@ -69,8 +75,8 @@ module Ransack
69
75
  end
70
76
 
71
77
  it 'creates conditions for polymorphic belongs_to association attributes' do
72
- search = Search.new(Note, notable_of_Person_type_name_eq: 'Ernie')
73
- condition = search.base[:notable_of_Person_type_name_eq]
78
+ s = Search.new(Note, notable_of_Person_type_name_eq: 'Ernie')
79
+ condition = s.base[:notable_of_Person_type_name_eq]
74
80
  expect(condition).to be_a Nodes::Condition
75
81
  expect(condition.predicate.name).to eq 'eq'
76
82
  expect(condition.attributes.first.name)
@@ -80,9 +86,9 @@ module Ransack
80
86
 
81
87
  it 'creates conditions for multiple polymorphic belongs_to association
82
88
  attributes' do
83
- search = Search.new(Note,
89
+ s = Search.new(Note,
84
90
  notable_of_Person_type_name_or_notable_of_Article_type_title_eq: 'Ernie')
85
- condition = search.
91
+ condition = s.
86
92
  base[:notable_of_Person_type_name_or_notable_of_Article_type_title_eq]
87
93
  expect(condition).to be_a Nodes::Condition
88
94
  expect(condition.predicate.name).to eq 'eq'
@@ -93,15 +99,62 @@ module Ransack
93
99
  expect(condition.value).to eq 'Ernie'
94
100
  end
95
101
 
102
+ it 'creates conditions for aliased attributes',
103
+ if: Ransack::SUPPORTS_ATTRIBUTE_ALIAS do
104
+ s = Search.new(Person, full_name_eq: 'Ernie')
105
+ condition = s.base[:full_name_eq]
106
+ expect(condition).to be_a Nodes::Condition
107
+ expect(condition.predicate.name).to eq 'eq'
108
+ expect(condition.attributes.first.name).to eq 'full_name'
109
+ expect(condition.value).to eq 'Ernie'
110
+ end
111
+
96
112
  it 'preserves default scope and conditions for associations' do
97
- search = Search.new(Person, published_articles_title_eq: 'Test')
98
- expect(search.result.to_sql).to include 'default_scope'
99
- expect(search.result.to_sql).to include 'published'
113
+ s = Search.new(Person, published_articles_title_eq: 'Test')
114
+ expect(s.result.to_sql).to include 'default_scope'
115
+ expect(s.result.to_sql).to include 'published'
116
+ end
117
+
118
+ # The failure/oversight in Ransack::Nodes::Condition#arel_predicate or deeper is beyond my understanding of the structures
119
+ it 'preserves (inverts) default scope and conditions for negative subqueries' do
120
+ # the positive case (published_articles_title_eq) is
121
+ # SELECT "people".* FROM "people"
122
+ # LEFT OUTER JOIN "articles" ON "articles"."person_id" = "people"."id"
123
+ # AND "articles"."published" = 't'
124
+ # AND ('default_scope' = 'default_scope')
125
+ # WHERE "articles"."title" = 'Test' ORDER BY "people"."id" DESC
126
+ #
127
+ # negative case was
128
+ # SELECT "people".* FROM "people" WHERE "people"."id" NOT IN (
129
+ # SELECT "articles"."person_id" FROM "articles"
130
+ # WHERE "articles"."person_id" = "people"."id"
131
+ # AND NOT ("articles"."title" != 'Test')
132
+ # ) ORDER BY "people"."id" DESC
133
+ #
134
+ # Should have been like
135
+ # SELECT "people".* FROM "people" WHERE "people"."id" NOT IN (
136
+ # SELECT "articles"."person_id" FROM "articles"
137
+ # WHERE "articles"."person_id" = "people"."id"
138
+ # AND "articles"."title" = 'Test' AND "articles"."published" = 't' AND ('default_scope' = 'default_scope')
139
+ # ) ORDER BY "people"."id" DESC
140
+ #
141
+ # With tenanting (eg default_scope with column reference), NOT IN should be like
142
+ # SELECT "people".* FROM "people" WHERE "people"."tenant_id" = 'tenant_id' AND "people"."id" NOT IN (
143
+ # SELECT "articles"."person_id" FROM "articles"
144
+ # WHERE "articles"."person_id" = "people"."id"
145
+ # AND "articles"."tenant_id" = 'tenant_id'
146
+ # AND "articles"."title" = 'Test' AND "articles"."published" = 't' AND ('default_scope' = 'default_scope')
147
+ # ) ORDER BY "people"."id" DESC
148
+
149
+ pending("spec should pass, but I do not know how/where to fix lib code")
150
+ s = Search.new(Person, published_articles_title_not_eq: 'Test')
151
+ expect(s.result.to_sql).to include 'default_scope'
152
+ expect(s.result.to_sql).to include 'published'
100
153
  end
101
154
 
102
155
  it 'discards empty conditions' do
103
- search = Search.new(Person, children_name_eq: '')
104
- condition = search.base[:children_name_eq]
156
+ s = Search.new(Person, children_name_eq: '')
157
+ condition = s.base[:children_name_eq]
105
158
  expect(condition).to be_nil
106
159
  end
107
160
 
@@ -111,13 +164,13 @@ module Ransack
111
164
  end
112
165
 
113
166
  it 'accepts arrays of groupings' do
114
- search = Search.new(Person,
167
+ s = Search.new(Person,
115
168
  g: [
116
169
  { m: 'or', name_eq: 'Ernie', children_name_eq: 'Ernie' },
117
170
  { m: 'or', name_eq: 'Bert', children_name_eq: 'Bert' },
118
171
  ]
119
172
  )
120
- ors = search.groupings
173
+ ors = s.groupings
121
174
  expect(ors.size).to eq(2)
122
175
  or1, or2 = ors
123
176
  expect(or1).to be_a Nodes::Grouping
@@ -126,14 +179,14 @@ module Ransack
126
179
  expect(or2.combinator).to eq 'or'
127
180
  end
128
181
 
129
- it 'accepts "attributes" hashes for groupings' do
130
- search = Search.new(Person,
182
+ it 'accepts attributes hashes for groupings' do
183
+ s = Search.new(Person,
131
184
  g: {
132
185
  '0' => { m: 'or', name_eq: 'Ernie', children_name_eq: 'Ernie' },
133
186
  '1' => { m: 'or', name_eq: 'Bert', children_name_eq: 'Bert' },
134
187
  }
135
188
  )
136
- ors = search.groupings
189
+ ors = s.groupings
137
190
  expect(ors.size).to eq(2)
138
191
  or1, or2 = ors
139
192
  expect(or1).to be_a Nodes::Grouping
@@ -142,8 +195,8 @@ module Ransack
142
195
  expect(or2.combinator).to eq 'or'
143
196
  end
144
197
 
145
- it 'accepts "attributes" hashes for conditions' do
146
- search = Search.new(Person,
198
+ it 'accepts attributes hashes for conditions' do
199
+ s = Search.new(Person,
147
200
  c: {
148
201
  '0' => { a: ['name'], p: 'eq', v: ['Ernie'] },
149
202
  '1' => {
@@ -152,7 +205,7 @@ module Ransack
152
205
  }
153
206
  }
154
207
  )
155
- conditions = search.base.conditions
208
+ conditions = s.base.conditions
156
209
  expect(conditions.size).to eq(2)
157
210
  expect(conditions.map { |c| c.class })
158
211
  .to eq [Nodes::Condition, Nodes::Condition]
@@ -163,8 +216,8 @@ module Ransack
163
216
  config.add_predicate 'ary_pred', wants_array: true
164
217
  end
165
218
 
166
- search = Search.new(Person, name_ary_pred: ['Ernie', 'Bert'])
167
- condition = search.base[:name_ary_pred]
219
+ s = Search.new(Person, name_ary_pred: ['Ernie', 'Bert'])
220
+ condition = s.base[:name_ary_pred]
168
221
  expect(condition).to be_a Nodes::Condition
169
222
  expect(condition.predicate.name).to eq 'ary_pred'
170
223
  expect(condition.attributes.first.name).to eq 'name'
@@ -172,14 +225,14 @@ module Ransack
172
225
  end
173
226
 
174
227
  it 'does not evaluate the query on #inspect' do
175
- search = Search.new(Person, children_id_in: [1, 2, 3])
176
- expect(search.inspect).not_to match /ActiveRecord/
228
+ s = Search.new(Person, children_id_in: [1, 2, 3])
229
+ expect(s.inspect).not_to match /ActiveRecord/
177
230
  end
178
231
 
179
232
  context 'with an invalid condition' do
180
233
  subject { Search.new(Person, unknown_attr_eq: 'Ernie') }
181
234
 
182
- context 'when ignore_unknown_conditions is false' do
235
+ context 'when ignore_unknown_conditions configuration option is false' do
183
236
  before do
184
237
  Ransack.configure { |c| c.ignore_unknown_conditions = false }
185
238
  end
@@ -187,13 +240,39 @@ module Ransack
187
240
  specify { expect { subject }.to raise_error ArgumentError }
188
241
  end
189
242
 
190
- context 'when ignore_unknown_conditions is true' do
243
+ context 'when ignore_unknown_conditions configuration option is true' do
191
244
  before do
192
245
  Ransack.configure { |c| c.ignore_unknown_conditions = true }
193
246
  end
194
247
 
195
248
  specify { expect { subject }.not_to raise_error }
196
249
  end
250
+
251
+ subject(:with_ignore_unknown_conditions_false) {
252
+ Search.new(Person,
253
+ { unknown_attr_eq: 'Ernie' },
254
+ { ignore_unknown_conditions: false }
255
+ )
256
+ }
257
+
258
+ subject(:with_ignore_unknown_conditions_true) {
259
+ Search.new(Person,
260
+ { unknown_attr_eq: 'Ernie' },
261
+ { ignore_unknown_conditions: true }
262
+ )
263
+ }
264
+
265
+ context 'when ignore_unknown_conditions search parameter is absent' do
266
+ specify { expect { subject }.not_to raise_error }
267
+ end
268
+
269
+ context 'when ignore_unknown_conditions search parameter is false' do
270
+ specify { expect { with_ignore_unknown_conditions_false }.to raise_error ArgumentError }
271
+ end
272
+
273
+ context 'when ignore_unknown_conditions search parameter is true' do
274
+ specify { expect { with_ignore_unknown_conditions_true }.not_to raise_error }
275
+ end
197
276
  end
198
277
 
199
278
  it 'does not modify the parameters' do
@@ -210,62 +289,86 @@ module Ransack
210
289
  let(:children_people_name_field) {
211
290
  "#{quote_table_name("children_people")}.#{quote_column_name("name")}"
212
291
  }
292
+ let(:notable_type_field) {
293
+ "#{quote_table_name("notes")}.#{quote_column_name("notable_type")}"
294
+ }
213
295
  it 'evaluates conditions contextually' do
214
- search = Search.new(Person, children_name_eq: 'Ernie')
215
- expect(search.result).to be_an ActiveRecord::Relation
216
- expect(search.result.to_sql).to match /#{
296
+ s = Search.new(Person, children_name_eq: 'Ernie')
297
+ expect(s.result).to be_an ActiveRecord::Relation
298
+ expect(s.result.to_sql).to match /#{
217
299
  children_people_name_field} = 'Ernie'/
218
300
  end
219
301
 
220
- # FIXME: Make this spec pass for Rails 4.1 / 4.2 / 5.0 and not just 4.0 by
221
- # commenting out lines 221 and 242 to run the test. Addresses issue #374.
222
- # https://github.com/activerecord-hackery/ransack/issues/374
223
- #
224
- if ::ActiveRecord::VERSION::STRING.first(3) == '4.0'
225
- it 'evaluates conditions for multiple belongs_to associations to the
226
- same table contextually' do
227
- s = Search.new(Recommendation,
228
- person_name_eq: 'Ernie',
229
- target_person_parent_name_eq: 'Test'
230
- ).result
231
- expect(s).to be_an ActiveRecord::Relation
232
- real_query = remove_quotes_and_backticks(s.to_sql)
233
- expected_query = <<-SQL
234
- SELECT recommendations.* FROM recommendations
235
- LEFT OUTER JOIN people ON people.id = recommendations.person_id
236
- LEFT OUTER JOIN people target_people_recommendations
237
- ON target_people_recommendations.id = recommendations.target_person_id
238
- LEFT OUTER JOIN people parents_people
239
- ON parents_people.id = target_people_recommendations.parent_id
240
- WHERE ((people.name = 'Ernie' AND parents_people.name = 'Test'))
241
- SQL
242
- .squish
243
- expect(real_query).to eq expected_query
244
- end
302
+ 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
+ s = Search.new(Person, {
306
+ name_eq: "person_name_query",
307
+ articles_title_eq: "person_article_title_query",
308
+ parent_name_eq: "parent_name_query",
309
+ parent_articles_title_eq: 'parents_article_title_query'
310
+ }).result
311
+
312
+ real_query = remove_quotes_and_backticks(s.to_sql)
313
+
314
+ expect(real_query)
315
+ .to match(%r{LEFT OUTER JOIN articles ON (\('default_scope' = 'default_scope'\) AND )?articles.person_id = people.id})
316
+ expect(real_query)
317
+ .to match(%r{LEFT OUTER JOIN articles articles_people ON (\('default_scope' = 'default_scope'\) AND )?articles_people.person_id = parents_people.id})
318
+
319
+ expect(real_query)
320
+ .to include "people.name = 'person_name_query'"
321
+ expect(real_query)
322
+ .to include "articles.title = 'person_article_title_query'"
323
+ expect(real_query)
324
+ .to include "parents_people.name = 'parent_name_query'"
325
+ expect(real_query)
326
+ .to include "articles_people.title = 'parents_article_title_query'"
327
+ end
328
+
329
+ it 'evaluates conditions for multiple `belongs_to` associations to the same table contextually' do
330
+ s = Search.new(
331
+ Recommendation,
332
+ person_name_eq: 'Ernie',
333
+ target_person_parent_name_eq: 'Test'
334
+ ).result
335
+ expect(s).to be_an ActiveRecord::Relation
336
+ real_query = remove_quotes_and_backticks(s.to_sql)
337
+ expected_query = <<-SQL
338
+ SELECT recommendations.* FROM recommendations
339
+ LEFT OUTER JOIN people ON people.id = recommendations.person_id
340
+ LEFT OUTER JOIN people target_people_recommendations
341
+ ON target_people_recommendations.id = recommendations.target_person_id
342
+ LEFT OUTER JOIN people parents_people
343
+ ON parents_people.id = target_people_recommendations.parent_id
344
+ WHERE (people.name = 'Ernie' AND parents_people.name = 'Test')
345
+ SQL
346
+ .squish
347
+ expect(real_query).to eq expected_query
245
348
  end
246
349
 
247
350
  it 'evaluates compound conditions contextually' do
248
- search = Search.new(Person, children_name_or_name_eq: 'Ernie').result
249
- expect(search).to be_an ActiveRecord::Relation
250
- expect(search.to_sql).to match /#{children_people_name_field
351
+ s = Search.new(Person, children_name_or_name_eq: 'Ernie').result
352
+ expect(s).to be_an ActiveRecord::Relation
353
+ expect(s.to_sql).to match /#{children_people_name_field
251
354
  } = 'Ernie' OR #{people_name_field} = 'Ernie'/
252
355
  end
253
356
 
254
357
  it 'evaluates polymorphic belongs_to association conditions contextually' do
255
- search = Search.new(Note, notable_of_Person_type_name_eq: 'Ernie')
256
- .result
257
- expect(search).to be_an ActiveRecord::Relation
258
- expect(search.to_sql).to match /#{people_name_field} = 'Ernie'/
358
+ s = Search.new(Note, notable_of_Person_type_name_eq: 'Ernie').result
359
+ expect(s).to be_an ActiveRecord::Relation
360
+ expect(s.to_sql).to match /#{people_name_field} = 'Ernie'/
361
+ expect(s.to_sql).to match /#{notable_type_field} = 'Person'/
259
362
  end
260
363
 
261
364
  it 'evaluates nested conditions' do
262
- search = Search.new(Person, children_name_eq: 'Ernie',
365
+ s = Search.new(Person, children_name_eq: 'Ernie',
263
366
  g: [
264
367
  { m: 'or', name_eq: 'Ernie', children_children_name_eq: 'Ernie' }
265
368
  ]
266
369
  ).result
267
- expect(search).to be_an ActiveRecord::Relation
268
- first, last = search.to_sql.split(/ AND /)
370
+ expect(s).to be_an ActiveRecord::Relation
371
+ first, last = s.to_sql.split(/ AND /)
269
372
  expect(first).to match /#{children_people_name_field} = 'Ernie'/
270
373
  expect(last).to match /#{
271
374
  people_name_field} = 'Ernie' OR #{
@@ -274,14 +377,14 @@ module Ransack
274
377
  end
275
378
 
276
379
  it 'evaluates arrays of groupings' do
277
- search = Search.new(Person,
380
+ s = Search.new(Person,
278
381
  g: [
279
382
  { m: 'or', name_eq: 'Ernie', children_name_eq: 'Ernie' },
280
383
  { m: 'or', name_eq: 'Bert', children_name_eq: 'Bert' }
281
384
  ]
282
385
  ).result
283
- expect(search).to be_an ActiveRecord::Relation
284
- first, last = search.to_sql.split(/ AND /)
386
+ expect(s).to be_an ActiveRecord::Relation
387
+ first, last = s.to_sql.split(/ AND /)
285
388
  expect(first).to match /#{people_name_field} = 'Ernie' OR #{
286
389
  children_people_name_field} = 'Ernie'/
287
390
  expect(last).to match /#{people_name_field} = 'Bert' OR #{
@@ -289,22 +392,24 @@ module Ransack
289
392
  end
290
393
 
291
394
  it 'returns distinct records when passed distinct: true' do
292
- search = Search.new(Person,
395
+ s = Search.new(Person,
293
396
  g: [
294
397
  { m: 'or', comments_body_cont: 'e', articles_comments_body_cont: 'e' }
295
398
  ]
296
399
  )
297
- if ActiveRecord::VERSION::MAJOR == 3
298
- all_or_load, uniq_or_distinct = :all, :uniq
299
- else
300
- all_or_load, uniq_or_distinct = :load, :distinct
301
- end
302
- expect(search.result.send(all_or_load).size)
400
+
401
+ all_or_load, uniq_or_distinct = :load, :distinct
402
+ expect(s.result.send(all_or_load).size)
303
403
  .to eq(9000)
304
- expect(search.result(distinct: true).size)
404
+ expect(s.result(distinct: true).size)
305
405
  .to eq(10)
306
- expect(search.result.send(all_or_load).send(uniq_or_distinct))
307
- .to eq search.result(distinct: true).send(all_or_load)
406
+ expect(s.result.send(all_or_load).send(uniq_or_distinct))
407
+ .to eq s.result(distinct: true).send(all_or_load)
408
+ end
409
+
410
+ it 'evaluates joins with belongs_to join' do
411
+ s = Person.joins(:parent).ransack(parent_name_eq: 'Ernie').result(distinct: true)
412
+ expect(s).to be_an ActiveRecord::Relation
308
413
  end
309
414
 
310
415
  private
@@ -428,6 +533,27 @@ module Ransack
428
533
  @s.sorts = 'id asc'
429
534
  expect(@s.result.first.id).to eq 1
430
535
  end
536
+
537
+ it "PG's sort option", if: ::ActiveRecord::Base.connection.adapter_name == "PostgreSQL" do
538
+ default = Ransack.options.clone
539
+
540
+ s = Search.new(Person, s: 'name asc')
541
+ expect(s.result.to_sql).to eq "SELECT \"people\".* FROM \"people\" ORDER BY \"people\".\"name\" ASC"
542
+
543
+ Ransack.configure { |c| c.postgres_fields_sort_option = :nulls_first }
544
+ s = Search.new(Person, s: 'name asc')
545
+ expect(s.result.to_sql).to eq "SELECT \"people\".* FROM \"people\" ORDER BY \"people\".\"name\" ASC NULLS FIRST"
546
+ s = Search.new(Person, s: 'name desc')
547
+ expect(s.result.to_sql).to eq "SELECT \"people\".* FROM \"people\" ORDER BY \"people\".\"name\" DESC NULLS LAST"
548
+
549
+ Ransack.configure { |c| c.postgres_fields_sort_option = :nulls_last }
550
+ s = Search.new(Person, s: 'name asc')
551
+ expect(s.result.to_sql).to eq "SELECT \"people\".* FROM \"people\" ORDER BY \"people\".\"name\" ASC NULLS LAST"
552
+ s = Search.new(Person, s: 'name desc')
553
+ expect(s.result.to_sql).to eq "SELECT \"people\".* FROM \"people\" ORDER BY \"people\".\"name\" DESC NULLS FIRST"
554
+
555
+ Ransack.options = default
556
+ end
431
557
  end
432
558
 
433
559
  describe '#method_missing' do
data/spec/spec_helper.rb CHANGED
@@ -1,9 +1,15 @@
1
1
  require 'machinist/active_record'
2
+ require 'polyamorous/polyamorous.rb'
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')]
@@ -35,12 +41,14 @@ RSpec.configure do |config|
35
41
  line = '=' * message.length
36
42
  puts line, message, line
37
43
  Schema.create
44
+ SubDB::Schema.create
38
45
  end
39
46
 
40
47
  config.before(:all) { Sham.reset(:before_all) }
41
48
  config.before(:each) { Sham.reset(:before_each) }
42
49
 
43
50
  config.include RansackHelper
51
+ config.include PolyamorousHelper
44
52
  end
45
53
 
46
54
  RSpec::Matchers.define :be_like do |expected|