ransack 1.7.0 → 2.4.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 (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|