mini-fast-sys 0.0.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.
- checksums.yaml +7 -0
- data/mini-fast-sys.gemspec +12 -0
- data/ransack-4.4.1/LICENSE +20 -0
- data/ransack-4.4.1/README.md +101 -0
- data/ransack-4.4.1/lib/polyamorous/activerecord/join_association.rb +70 -0
- data/ransack-4.4.1/lib/polyamorous/activerecord/join_association_7_2.rb +55 -0
- data/ransack-4.4.1/lib/polyamorous/activerecord/join_dependency.rb +102 -0
- data/ransack-4.4.1/lib/polyamorous/activerecord/reflection.rb +11 -0
- data/ransack-4.4.1/lib/polyamorous/join.rb +70 -0
- data/ransack-4.4.1/lib/polyamorous/polyamorous.rb +27 -0
- data/ransack-4.4.1/lib/polyamorous/swapping_reflection_class.rb +11 -0
- data/ransack-4.4.1/lib/polyamorous/tree_node.rb +7 -0
- data/ransack-4.4.1/lib/ransack/active_record.rb +14 -0
- data/ransack-4.4.1/lib/ransack/adapters/active_record/base.rb +152 -0
- data/ransack-4.4.1/lib/ransack/adapters/active_record/context.rb +373 -0
- data/ransack-4.4.1/lib/ransack/configuration.rb +209 -0
- data/ransack-4.4.1/lib/ransack/constants.rb +176 -0
- data/ransack-4.4.1/lib/ransack/context.rb +190 -0
- data/ransack-4.4.1/lib/ransack/helpers/form_builder.rb +277 -0
- data/ransack-4.4.1/lib/ransack/helpers/form_helper.rb +311 -0
- data/ransack-4.4.1/lib/ransack/helpers.rb +2 -0
- data/ransack-4.4.1/lib/ransack/invalid_search_error.rb +3 -0
- data/ransack-4.4.1/lib/ransack/locale/ar.yml +70 -0
- data/ransack-4.4.1/lib/ransack/locale/az.yml +70 -0
- data/ransack-4.4.1/lib/ransack/locale/bg.yml +70 -0
- data/ransack-4.4.1/lib/ransack/locale/ca.yml +70 -0
- data/ransack-4.4.1/lib/ransack/locale/cs.yml +70 -0
- data/ransack-4.4.1/lib/ransack/locale/da.yml +70 -0
- data/ransack-4.4.1/lib/ransack/locale/de.yml +70 -0
- data/ransack-4.4.1/lib/ransack/locale/el.yml +70 -0
- data/ransack-4.4.1/lib/ransack/locale/en.yml +70 -0
- data/ransack-4.4.1/lib/ransack/locale/es.yml +70 -0
- data/ransack-4.4.1/lib/ransack/locale/fa.yml +70 -0
- data/ransack-4.4.1/lib/ransack/locale/fi.yml +71 -0
- data/ransack-4.4.1/lib/ransack/locale/fr.yml +70 -0
- data/ransack-4.4.1/lib/ransack/locale/hu.yml +70 -0
- data/ransack-4.4.1/lib/ransack/locale/id.yml +70 -0
- data/ransack-4.4.1/lib/ransack/locale/it.yml +70 -0
- data/ransack-4.4.1/lib/ransack/locale/ja.yml +70 -0
- data/ransack-4.4.1/lib/ransack/locale/ko.yml +70 -0
- data/ransack-4.4.1/lib/ransack/locale/nl.yml +70 -0
- data/ransack-4.4.1/lib/ransack/locale/pt-BR.yml +70 -0
- data/ransack-4.4.1/lib/ransack/locale/ro.yml +70 -0
- data/ransack-4.4.1/lib/ransack/locale/ru.yml +70 -0
- data/ransack-4.4.1/lib/ransack/locale/sk.yml +70 -0
- data/ransack-4.4.1/lib/ransack/locale/sv.yml +70 -0
- data/ransack-4.4.1/lib/ransack/locale/tr.yml +70 -0
- data/ransack-4.4.1/lib/ransack/locale/uk.yml +72 -0
- data/ransack-4.4.1/lib/ransack/locale/zh-CN.yml +70 -0
- data/ransack-4.4.1/lib/ransack/locale/zh-TW.yml +70 -0
- data/ransack-4.4.1/lib/ransack/naming.rb +58 -0
- data/ransack-4.4.1/lib/ransack/nodes/attribute.rb +59 -0
- data/ransack-4.4.1/lib/ransack/nodes/bindable.rb +52 -0
- data/ransack-4.4.1/lib/ransack/nodes/condition.rb +399 -0
- data/ransack-4.4.1/lib/ransack/nodes/grouping.rb +206 -0
- data/ransack-4.4.1/lib/ransack/nodes/node.rb +34 -0
- data/ransack-4.4.1/lib/ransack/nodes/sort.rb +54 -0
- data/ransack-4.4.1/lib/ransack/nodes/value.rb +115 -0
- data/ransack-4.4.1/lib/ransack/predicate.rb +75 -0
- data/ransack-4.4.1/lib/ransack/ransacker.rb +24 -0
- data/ransack-4.4.1/lib/ransack/search.rb +195 -0
- data/ransack-4.4.1/lib/ransack/translate.rb +156 -0
- data/ransack-4.4.1/lib/ransack/version.rb +3 -0
- data/ransack-4.4.1/lib/ransack/visitor.rb +87 -0
- data/ransack-4.4.1/lib/ransack.rb +33 -0
- data/ransack-4.4.1/spec/console.rb +13 -0
- data/ransack-4.4.1/spec/factories/articles.rb +7 -0
- data/ransack-4.4.1/spec/factories/comments.rb +7 -0
- data/ransack-4.4.1/spec/factories/notes.rb +13 -0
- data/ransack-4.4.1/spec/factories/people.rb +10 -0
- data/ransack-4.4.1/spec/factories/tags.rb +5 -0
- data/ransack-4.4.1/spec/helpers/polyamorous_helper.rb +13 -0
- data/ransack-4.4.1/spec/helpers/ransack_helper.rb +9 -0
- data/ransack-4.4.1/spec/polyamorous/activerecord_compatibility_spec.rb +15 -0
- data/ransack-4.4.1/spec/polyamorous/join_association_spec.rb +29 -0
- data/ransack-4.4.1/spec/polyamorous/join_dependency_spec.rb +80 -0
- data/ransack-4.4.1/spec/polyamorous/join_spec.rb +19 -0
- data/ransack-4.4.1/spec/ransack/adapters/active_record/base_spec.rb +943 -0
- data/ransack-4.4.1/spec/ransack/adapters/active_record/context_spec.rb +219 -0
- data/ransack-4.4.1/spec/ransack/configuration_spec.rb +201 -0
- data/ransack-4.4.1/spec/ransack/helpers/form_builder_spec.rb +169 -0
- data/ransack-4.4.1/spec/ransack/helpers/form_helper_spec.rb +1075 -0
- data/ransack-4.4.1/spec/ransack/invalid_search_error_spec.rb +27 -0
- data/ransack-4.4.1/spec/ransack/nodes/condition_spec.rb +333 -0
- data/ransack-4.4.1/spec/ransack/nodes/grouping_spec.rb +111 -0
- data/ransack-4.4.1/spec/ransack/nodes/value_spec.rb +126 -0
- data/ransack-4.4.1/spec/ransack/predicate_spec.rb +504 -0
- data/ransack-4.4.1/spec/ransack/ransacker_spec.rb +69 -0
- data/ransack-4.4.1/spec/ransack/search_spec.rb +821 -0
- data/ransack-4.4.1/spec/ransack/translate_spec.rb +16 -0
- data/ransack-4.4.1/spec/spec_helper.rb +52 -0
- data/ransack-4.4.1/spec/support/en.yml +18 -0
- data/ransack-4.4.1/spec/support/schema.rb +419 -0
- metadata +133 -0
|
@@ -0,0 +1,821 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
module Ransack
|
|
4
|
+
describe Search do
|
|
5
|
+
describe '#initialize' do
|
|
6
|
+
it 'removes empty conditions before building' do
|
|
7
|
+
expect_any_instance_of(Search).to receive(:build).with({})
|
|
8
|
+
Search.new(Person, name_eq: '')
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
it 'keeps conditions with a false value before building' do
|
|
12
|
+
expect_any_instance_of(Search).to receive(:build)
|
|
13
|
+
.with({ 'name_eq' => false })
|
|
14
|
+
Search.new(Person, name_eq: false)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
it 'keeps conditions with a value before building' do
|
|
18
|
+
expect_any_instance_of(Search).to receive(:build)
|
|
19
|
+
.with({ 'name_eq' => 'foobar' })
|
|
20
|
+
Search.new(Person, name_eq: 'foobar')
|
|
21
|
+
end
|
|
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
|
+
|
|
61
|
+
it 'removes empty suffixed conditions before building' do
|
|
62
|
+
expect_any_instance_of(Search).to receive(:build).with({})
|
|
63
|
+
Search.new(Person, name_eq_any: [''])
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
it 'keeps suffixed conditions with a false value before building' do
|
|
67
|
+
expect_any_instance_of(Search).to receive(:build)
|
|
68
|
+
.with({ 'name_eq_any' => [false] })
|
|
69
|
+
Search.new(Person, name_eq_any: [false])
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
it 'keeps suffixed conditions with a value before building' do
|
|
73
|
+
expect_any_instance_of(Search).to receive(:build)
|
|
74
|
+
.with({ 'name_eq_any' => ['foobar'] })
|
|
75
|
+
Search.new(Person, name_eq_any: ['foobar'])
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
it 'does not raise exception for string :params argument' do
|
|
79
|
+
expect { Search.new(Person, '') }.not_to raise_error
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
it 'accepts a context option' do
|
|
83
|
+
shared_context = Context.for(Person)
|
|
84
|
+
s1 = Search.new(Person, { name_eq: 'A' }, context: shared_context)
|
|
85
|
+
s2 = Search.new(Person, { name_eq: 'B' }, context: shared_context)
|
|
86
|
+
expect(s1.context).to be s2.context
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
describe '#build' do
|
|
91
|
+
it 'creates conditions for top-level attributes' do
|
|
92
|
+
s = Search.new(Person, name_eq: 'Ernie')
|
|
93
|
+
condition = s.base[:name_eq]
|
|
94
|
+
expect(condition).to be_a Nodes::Condition
|
|
95
|
+
expect(condition.predicate.name).to eq 'eq'
|
|
96
|
+
expect(condition.attributes.first.name).to eq 'name'
|
|
97
|
+
expect(condition.value).to eq 'Ernie'
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
it 'creates conditions for association attributes' do
|
|
101
|
+
s = Search.new(Person, children_name_eq: 'Ernie')
|
|
102
|
+
condition = s.base[:children_name_eq]
|
|
103
|
+
expect(condition).to be_a Nodes::Condition
|
|
104
|
+
expect(condition.predicate.name).to eq 'eq'
|
|
105
|
+
expect(condition.attributes.first.name).to eq 'children_name'
|
|
106
|
+
expect(condition.value).to eq 'Ernie'
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
it 'creates conditions for polymorphic belongs_to association attributes' do
|
|
110
|
+
s = Search.new(Note, notable_of_Person_type_name_eq: 'Ernie')
|
|
111
|
+
condition = s.base[:notable_of_Person_type_name_eq]
|
|
112
|
+
expect(condition).to be_a Nodes::Condition
|
|
113
|
+
expect(condition.predicate.name).to eq 'eq'
|
|
114
|
+
expect(condition.attributes.first.name)
|
|
115
|
+
.to eq 'notable_of_Person_type_name'
|
|
116
|
+
expect(condition.value).to eq 'Ernie'
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
it 'creates conditions for multiple polymorphic belongs_to association
|
|
120
|
+
attributes' do
|
|
121
|
+
s = Search.new(Note,
|
|
122
|
+
notable_of_Person_type_name_or_notable_of_Article_type_title_eq: 'Ernie')
|
|
123
|
+
condition = s.
|
|
124
|
+
base[:notable_of_Person_type_name_or_notable_of_Article_type_title_eq]
|
|
125
|
+
expect(condition).to be_a Nodes::Condition
|
|
126
|
+
expect(condition.predicate.name).to eq 'eq'
|
|
127
|
+
expect(condition.attributes.first.name)
|
|
128
|
+
.to eq 'notable_of_Person_type_name'
|
|
129
|
+
expect(condition.attributes.last.name)
|
|
130
|
+
.to eq 'notable_of_Article_type_title'
|
|
131
|
+
expect(condition.value).to eq 'Ernie'
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
it 'creates conditions for aliased attributes',
|
|
135
|
+
if: Ransack::SUPPORTS_ATTRIBUTE_ALIAS do
|
|
136
|
+
s = Search.new(Person, full_name_eq: 'Ernie')
|
|
137
|
+
condition = s.base[:full_name_eq]
|
|
138
|
+
expect(condition).to be_a Nodes::Condition
|
|
139
|
+
expect(condition.predicate.name).to eq 'eq'
|
|
140
|
+
expect(condition.attributes.first.name).to eq 'full_name'
|
|
141
|
+
expect(condition.value).to eq 'Ernie'
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
it 'preserves default scope and conditions for associations' do
|
|
145
|
+
s = Search.new(Person, published_articles_title_eq: 'Test')
|
|
146
|
+
expect(s.result.to_sql).to include 'default_scope'
|
|
147
|
+
expect(s.result.to_sql).to include 'published'
|
|
148
|
+
end
|
|
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
|
+
s = Search.new(Person, published_articles_title_not_eq: 'Test')
|
|
182
|
+
expect(s.result.to_sql).to include 'default_scope'
|
|
183
|
+
expect(s.result.to_sql).to include 'published'
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
it 'discards empty conditions' do
|
|
187
|
+
s = Search.new(Person, children_name_eq: '')
|
|
188
|
+
condition = s.base[:children_name_eq]
|
|
189
|
+
expect(condition).to be_nil
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
it 'accepts base grouping condition as an option' do
|
|
193
|
+
expect(Nodes::Grouping).to receive(:new).with(kind_of(Context), 'or')
|
|
194
|
+
Search.new(Person, {}, { grouping: 'or' })
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
it 'accepts arrays of groupings' do
|
|
198
|
+
s = Search.new(Person,
|
|
199
|
+
g: [
|
|
200
|
+
{ m: 'or', name_eq: 'Ernie', children_name_eq: 'Ernie' },
|
|
201
|
+
{ m: 'or', name_eq: 'Bert', children_name_eq: 'Bert' },
|
|
202
|
+
]
|
|
203
|
+
)
|
|
204
|
+
ors = s.groupings
|
|
205
|
+
expect(ors.size).to eq(2)
|
|
206
|
+
or1, or2 = ors
|
|
207
|
+
expect(or1).to be_a Nodes::Grouping
|
|
208
|
+
expect(or1.combinator).to eq 'or'
|
|
209
|
+
expect(or2).to be_a Nodes::Grouping
|
|
210
|
+
expect(or2.combinator).to eq 'or'
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
it 'accepts attributes hashes for groupings' do
|
|
214
|
+
s = Search.new(Person,
|
|
215
|
+
g: {
|
|
216
|
+
'0' => { m: 'or', name_eq: 'Ernie', children_name_eq: 'Ernie' },
|
|
217
|
+
'1' => { m: 'or', name_eq: 'Bert', children_name_eq: 'Bert' },
|
|
218
|
+
}
|
|
219
|
+
)
|
|
220
|
+
ors = s.groupings
|
|
221
|
+
expect(ors.size).to eq(2)
|
|
222
|
+
or1, or2 = ors
|
|
223
|
+
expect(or1).to be_a Nodes::Grouping
|
|
224
|
+
expect(or1.combinator).to eq 'or'
|
|
225
|
+
expect(or2).to be_a Nodes::Grouping
|
|
226
|
+
expect(or2.combinator).to eq 'or'
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
it 'accepts attributes hashes for conditions' do
|
|
230
|
+
s = Search.new(Person,
|
|
231
|
+
c: {
|
|
232
|
+
'0' => { a: ['name'], p: 'eq', v: ['Ernie'] },
|
|
233
|
+
'1' => {
|
|
234
|
+
a: ['children_name', 'parent_name'],
|
|
235
|
+
p: 'eq', v: ['Ernie'], m: 'or'
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
)
|
|
239
|
+
conditions = s.base.conditions
|
|
240
|
+
expect(conditions.size).to eq(2)
|
|
241
|
+
expect(conditions.map { |c| c.class })
|
|
242
|
+
.to eq [Nodes::Condition, Nodes::Condition]
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
it 'creates conditions for custom predicates that take arrays' do
|
|
246
|
+
Ransack.configure do |config|
|
|
247
|
+
config.add_predicate 'ary_pred', wants_array: true
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
s = Search.new(Person, name_ary_pred: ['Ernie', 'Bert'])
|
|
251
|
+
condition = s.base[:name_ary_pred]
|
|
252
|
+
expect(condition).to be_a Nodes::Condition
|
|
253
|
+
expect(condition.predicate.name).to eq 'ary_pred'
|
|
254
|
+
expect(condition.attributes.first.name).to eq 'name'
|
|
255
|
+
expect(condition.value).to eq ['Ernie', 'Bert']
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
it 'does not evaluate the query on #inspect' do
|
|
259
|
+
s = Search.new(Person, children_id_in: [1, 2, 3])
|
|
260
|
+
expect(s.inspect).not_to match /ActiveRecord/
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
context 'with an invalid condition' do
|
|
264
|
+
subject { Search.new(Person, unknown_attr_eq: 'Ernie') }
|
|
265
|
+
|
|
266
|
+
context 'when ignore_unknown_conditions configuration option is false' do
|
|
267
|
+
before do
|
|
268
|
+
Ransack.configure { |c| c.ignore_unknown_conditions = false }
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
specify { expect { subject }.to raise_error ArgumentError }
|
|
272
|
+
specify { expect { subject }.to raise_error InvalidSearchError }
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
context 'when ignore_unknown_conditions configuration option is true' do
|
|
276
|
+
before do
|
|
277
|
+
Ransack.configure { |c| c.ignore_unknown_conditions = true }
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
specify { expect { subject }.not_to raise_error }
|
|
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
|
+
specify { expect { with_ignore_unknown_conditions_false }.to raise_error InvalidSearchError }
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
context 'when ignore_unknown_conditions search parameter is true' do
|
|
307
|
+
specify { expect { with_ignore_unknown_conditions_true }.not_to raise_error }
|
|
308
|
+
end
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
it 'does not modify the parameters' do
|
|
312
|
+
params = { name_eq: '' }
|
|
313
|
+
expect { Search.new(Person, params) }.not_to change { params }
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
context 'with empty search parameters' do
|
|
317
|
+
it 'handles completely empty parameters' do
|
|
318
|
+
search = Search.new(Person, {})
|
|
319
|
+
expect(search.result.to_sql).not_to match(/WHERE/)
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
it 'handles nil parameters' do
|
|
323
|
+
search = Search.new(Person, nil)
|
|
324
|
+
expect(search.result.to_sql).not_to match(/WHERE/)
|
|
325
|
+
end
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
context 'with whitespace-only values' do
|
|
329
|
+
before do
|
|
330
|
+
Ransack.configure { |c| c.strip_whitespace = true }
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
it 'removes whitespace-only values' do
|
|
334
|
+
expect_any_instance_of(Search).to receive(:build).with({})
|
|
335
|
+
Search.new(Person, name_eq: ' ')
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
it 'keeps values with content after whitespace stripping' do
|
|
339
|
+
expect_any_instance_of(Search).to receive(:build).with({ 'name_eq' => 'test' })
|
|
340
|
+
Search.new(Person, name_eq: ' test ')
|
|
341
|
+
end
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
context 'with special characters in values' do
|
|
345
|
+
it 'handles values with special regex characters' do
|
|
346
|
+
search = Search.new(Person, name_cont: 'test[(){}^$|?*+.\\')
|
|
347
|
+
expect { search.result }.not_to raise_error
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
it 'handles values with SQL injection attempts' do
|
|
351
|
+
search = Search.new(Person, name_cont: "'; DROP TABLE people; --")
|
|
352
|
+
expect { search.result }.not_to raise_error
|
|
353
|
+
end
|
|
354
|
+
end
|
|
355
|
+
|
|
356
|
+
context "ransackable_scope" do
|
|
357
|
+
around(:each) do |example|
|
|
358
|
+
Person.define_singleton_method(:name_eq) do |name|
|
|
359
|
+
self.where(name: name)
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
begin
|
|
363
|
+
example.run
|
|
364
|
+
ensure
|
|
365
|
+
Person.singleton_class.undef_method :name_eq
|
|
366
|
+
end
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
it "is prioritized over base predicates" do
|
|
370
|
+
allow(Person).to receive(:ransackable_scopes)
|
|
371
|
+
.and_return(Person.ransackable_scopes + [:name_eq])
|
|
372
|
+
|
|
373
|
+
s = Search.new(Person, name_eq: "Johny")
|
|
374
|
+
expect(s.instance_variable_get(:@scope_args)["name_eq"]).to eq("Johny")
|
|
375
|
+
expect(s.base[:name_eq]).to be_nil
|
|
376
|
+
end
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
context "ransackable_scope with array arguments" do
|
|
380
|
+
around(:each) do |example|
|
|
381
|
+
Person.define_singleton_method(:domestic) do |countries|
|
|
382
|
+
self.where(name: countries)
|
|
383
|
+
end
|
|
384
|
+
|
|
385
|
+
Person.define_singleton_method(:flexible_scope) do |*args|
|
|
386
|
+
self.where(id: args)
|
|
387
|
+
end
|
|
388
|
+
|
|
389
|
+
Person.define_singleton_method(:two_param_scope) do |param1, param2|
|
|
390
|
+
self.where(name: param1, id: param2)
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
begin
|
|
394
|
+
example.run
|
|
395
|
+
ensure
|
|
396
|
+
Person.singleton_class.undef_method :domestic
|
|
397
|
+
Person.singleton_class.undef_method :flexible_scope
|
|
398
|
+
Person.singleton_class.undef_method :two_param_scope
|
|
399
|
+
end
|
|
400
|
+
end
|
|
401
|
+
|
|
402
|
+
it "handles scopes that take arrays as single arguments (arity 1)" do
|
|
403
|
+
allow(Person).to receive(:ransackable_scopes)
|
|
404
|
+
.and_return(Person.ransackable_scopes + [:domestic])
|
|
405
|
+
|
|
406
|
+
# This should not raise ArgumentError
|
|
407
|
+
expect {
|
|
408
|
+
s = Search.new(Person, domestic: ['US', 'JP'])
|
|
409
|
+
s.result # This triggers the actual scope call
|
|
410
|
+
}.not_to raise_error
|
|
411
|
+
|
|
412
|
+
s = Search.new(Person, domestic: ['US', 'JP'])
|
|
413
|
+
expect(s.instance_variable_get(:@scope_args)["domestic"]).to eq("US")
|
|
414
|
+
end
|
|
415
|
+
|
|
416
|
+
it "handles scopes with flexible arity (negative arity)" do
|
|
417
|
+
allow(Person).to receive(:ransackable_scopes)
|
|
418
|
+
.and_return(Person.ransackable_scopes + [:flexible_scope])
|
|
419
|
+
|
|
420
|
+
expect {
|
|
421
|
+
s = Search.new(Person, flexible_scope: ['US', 'JP'])
|
|
422
|
+
s.result
|
|
423
|
+
}.not_to raise_error
|
|
424
|
+
end
|
|
425
|
+
|
|
426
|
+
it "handles scopes with arity > 1" do
|
|
427
|
+
allow(Person).to receive(:ransackable_scopes)
|
|
428
|
+
.and_return(Person.ransackable_scopes + [:two_param_scope])
|
|
429
|
+
|
|
430
|
+
expect {
|
|
431
|
+
s = Search.new(Person, two_param_scope: ['param1', 'param2'])
|
|
432
|
+
s.result
|
|
433
|
+
}.not_to raise_error
|
|
434
|
+
end
|
|
435
|
+
|
|
436
|
+
it "still supports the workaround with nested arrays" do
|
|
437
|
+
allow(Person).to receive(:ransackable_scopes)
|
|
438
|
+
.and_return(Person.ransackable_scopes + [:domestic])
|
|
439
|
+
|
|
440
|
+
# The workaround from the issue should still work
|
|
441
|
+
expect {
|
|
442
|
+
s = Search.new(Person, domestic: [['US', 'JP']])
|
|
443
|
+
s.result
|
|
444
|
+
}.not_to raise_error
|
|
445
|
+
end
|
|
446
|
+
end
|
|
447
|
+
end
|
|
448
|
+
|
|
449
|
+
describe '#result' do
|
|
450
|
+
let(:people_name_field) {
|
|
451
|
+
"#{quote_table_name("people")}.#{quote_column_name("name")}"
|
|
452
|
+
}
|
|
453
|
+
let(:children_people_name_field) {
|
|
454
|
+
"#{quote_table_name("children_people")}.#{quote_column_name("name")}"
|
|
455
|
+
}
|
|
456
|
+
let(:notable_type_field) {
|
|
457
|
+
"#{quote_table_name("notes")}.#{quote_column_name("notable_type")}"
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
it 'evaluates conditions contextually' do
|
|
461
|
+
s = Search.new(Person, children_name_eq: 'Ernie')
|
|
462
|
+
expect(s.result).to be_an ActiveRecord::Relation
|
|
463
|
+
expect(s.result.to_sql).to match /#{
|
|
464
|
+
children_people_name_field} = 'Ernie'/
|
|
465
|
+
end
|
|
466
|
+
|
|
467
|
+
it 'use appropriate table alias' do
|
|
468
|
+
s = Search.new(Person, {
|
|
469
|
+
name_eq: "person_name_query",
|
|
470
|
+
articles_title_eq: "person_article_title_query",
|
|
471
|
+
parent_name_eq: "parent_name_query",
|
|
472
|
+
parent_articles_title_eq: 'parents_article_title_query'
|
|
473
|
+
}).result
|
|
474
|
+
|
|
475
|
+
real_query = remove_quotes_and_backticks(s.to_sql)
|
|
476
|
+
|
|
477
|
+
expect(real_query)
|
|
478
|
+
.to match(%r{LEFT OUTER JOIN articles ON (\('default_scope' = 'default_scope'\) AND )?articles.person_id = people.id})
|
|
479
|
+
expect(real_query)
|
|
480
|
+
.to match(%r{LEFT OUTER JOIN articles articles_people ON (\('default_scope' = 'default_scope'\) AND )?articles_people.person_id = parents_people.id})
|
|
481
|
+
|
|
482
|
+
expect(real_query)
|
|
483
|
+
.to include "people.name = 'person_name_query'"
|
|
484
|
+
expect(real_query)
|
|
485
|
+
.to include "articles.title = 'person_article_title_query'"
|
|
486
|
+
expect(real_query)
|
|
487
|
+
.to include "parents_people.name = 'parent_name_query'"
|
|
488
|
+
expect(real_query)
|
|
489
|
+
.to include "articles_people.title = 'parents_article_title_query'"
|
|
490
|
+
end
|
|
491
|
+
|
|
492
|
+
it 'evaluates conditions for multiple `belongs_to` associations to the same table contextually' do
|
|
493
|
+
s = Search.new(
|
|
494
|
+
Recommendation,
|
|
495
|
+
person_name_eq: 'Ernie',
|
|
496
|
+
target_person_parent_name_eq: 'Test'
|
|
497
|
+
).result
|
|
498
|
+
expect(s).to be_an ActiveRecord::Relation
|
|
499
|
+
real_query = remove_quotes_and_backticks(s.to_sql)
|
|
500
|
+
expected_query = <<-SQL
|
|
501
|
+
SELECT recommendations.* FROM recommendations
|
|
502
|
+
LEFT OUTER JOIN people ON people.id = recommendations.person_id
|
|
503
|
+
LEFT OUTER JOIN people target_people_recommendations
|
|
504
|
+
ON target_people_recommendations.id = recommendations.target_person_id
|
|
505
|
+
LEFT OUTER JOIN people parents_people
|
|
506
|
+
ON parents_people.id = target_people_recommendations.parent_id
|
|
507
|
+
WHERE (people.name = 'Ernie' AND parents_people.name = 'Test')
|
|
508
|
+
SQL
|
|
509
|
+
.squish
|
|
510
|
+
expect(real_query).to eq expected_query
|
|
511
|
+
end
|
|
512
|
+
|
|
513
|
+
it 'evaluates compound conditions contextually' do
|
|
514
|
+
s = Search.new(Person, children_name_or_name_eq: 'Ernie').result
|
|
515
|
+
expect(s).to be_an ActiveRecord::Relation
|
|
516
|
+
expect(s.to_sql).to match /#{children_people_name_field
|
|
517
|
+
} = 'Ernie' OR #{people_name_field} = 'Ernie'/
|
|
518
|
+
end
|
|
519
|
+
|
|
520
|
+
it 'evaluates polymorphic belongs_to association conditions contextually' do
|
|
521
|
+
s = Search.new(Note, notable_of_Person_type_name_eq: 'Ernie').result
|
|
522
|
+
expect(s).to be_an ActiveRecord::Relation
|
|
523
|
+
expect(s.to_sql).to match /#{people_name_field} = 'Ernie'/
|
|
524
|
+
expect(s.to_sql).to match /#{notable_type_field} = 'Person'/
|
|
525
|
+
end
|
|
526
|
+
|
|
527
|
+
it 'evaluates nested conditions' do
|
|
528
|
+
s = Search.new(Person, children_name_eq: 'Ernie',
|
|
529
|
+
g: [
|
|
530
|
+
{ m: 'or', name_eq: 'Ernie', children_children_name_eq: 'Ernie' }
|
|
531
|
+
]
|
|
532
|
+
).result
|
|
533
|
+
expect(s).to be_an ActiveRecord::Relation
|
|
534
|
+
first, last = s.to_sql.split(/ AND /)
|
|
535
|
+
expect(first).to match /#{children_people_name_field} = 'Ernie'/
|
|
536
|
+
expect(last).to match /#{
|
|
537
|
+
people_name_field} = 'Ernie' OR #{
|
|
538
|
+
quote_table_name("children_people_2")}.#{
|
|
539
|
+
quote_column_name("name")} = 'Ernie'/
|
|
540
|
+
end
|
|
541
|
+
|
|
542
|
+
it 'evaluates arrays of groupings' do
|
|
543
|
+
s = Search.new(Person,
|
|
544
|
+
g: [
|
|
545
|
+
{ m: 'or', name_eq: 'Ernie', children_name_eq: 'Ernie' },
|
|
546
|
+
{ m: 'or', name_eq: 'Bert', children_name_eq: 'Bert' }
|
|
547
|
+
]
|
|
548
|
+
).result
|
|
549
|
+
expect(s).to be_an ActiveRecord::Relation
|
|
550
|
+
first, last = s.to_sql.split(/ AND /)
|
|
551
|
+
expect(first).to match /#{people_name_field} = 'Ernie' OR #{
|
|
552
|
+
children_people_name_field} = 'Ernie'/
|
|
553
|
+
expect(last).to match /#{people_name_field} = 'Bert' OR #{
|
|
554
|
+
children_people_name_field} = 'Bert'/
|
|
555
|
+
end
|
|
556
|
+
|
|
557
|
+
it 'returns distinct records when passed distinct: true' do
|
|
558
|
+
s = Search.new(Person,
|
|
559
|
+
g: [
|
|
560
|
+
{ m: 'or', comments_body_cont: 'e', articles_comments_body_cont: 'e' }
|
|
561
|
+
]
|
|
562
|
+
)
|
|
563
|
+
|
|
564
|
+
all_or_load, uniq_or_distinct = :load, :distinct
|
|
565
|
+
expect(s.result.send(all_or_load).size)
|
|
566
|
+
.to eq(8998)
|
|
567
|
+
expect(s.result(distinct: true).size)
|
|
568
|
+
.to eq(10)
|
|
569
|
+
expect(s.result.send(all_or_load).send(uniq_or_distinct))
|
|
570
|
+
.to eq s.result(distinct: true).send(all_or_load)
|
|
571
|
+
end
|
|
572
|
+
|
|
573
|
+
it 'evaluates joins with belongs_to join' do
|
|
574
|
+
s = Person.joins(:parent).ransack(parent_name_eq: 'Ernie').result(distinct: true)
|
|
575
|
+
expect(s).to be_an ActiveRecord::Relation
|
|
576
|
+
end
|
|
577
|
+
|
|
578
|
+
private
|
|
579
|
+
|
|
580
|
+
def remove_quotes_and_backticks(str)
|
|
581
|
+
str.gsub(/["`]/, '')
|
|
582
|
+
end
|
|
583
|
+
end
|
|
584
|
+
|
|
585
|
+
describe '#sorts=' do
|
|
586
|
+
before do
|
|
587
|
+
@s = Search.new(Person)
|
|
588
|
+
end
|
|
589
|
+
|
|
590
|
+
it 'doesn\'t creates sorts' do
|
|
591
|
+
@s.sorts = ''
|
|
592
|
+
expect(@s.sorts.size).to eq(0)
|
|
593
|
+
end
|
|
594
|
+
|
|
595
|
+
it 'creates sorts based on a single attribute/direction' do
|
|
596
|
+
@s.sorts = 'id desc'
|
|
597
|
+
expect(@s.sorts.size).to eq(1)
|
|
598
|
+
sort = @s.sorts.first
|
|
599
|
+
expect(sort).to be_a Nodes::Sort
|
|
600
|
+
expect(sort.name).to eq 'id'
|
|
601
|
+
expect(sort.dir).to eq 'desc'
|
|
602
|
+
end
|
|
603
|
+
|
|
604
|
+
it 'creates sorts based on a single attribute and uppercase direction' do
|
|
605
|
+
@s.sorts = 'id DESC'
|
|
606
|
+
expect(@s.sorts.size).to eq(1)
|
|
607
|
+
sort = @s.sorts.first
|
|
608
|
+
expect(sort).to be_a Nodes::Sort
|
|
609
|
+
expect(sort.name).to eq 'id'
|
|
610
|
+
expect(sort.dir).to eq 'desc'
|
|
611
|
+
end
|
|
612
|
+
|
|
613
|
+
it 'creates sorts based on a single attribute and without direction' do
|
|
614
|
+
@s.sorts = 'id'
|
|
615
|
+
expect(@s.sorts.size).to eq(1)
|
|
616
|
+
sort = @s.sorts.first
|
|
617
|
+
expect(sort).to be_a Nodes::Sort
|
|
618
|
+
expect(sort.name).to eq 'id'
|
|
619
|
+
expect(sort.dir).to eq 'asc'
|
|
620
|
+
end
|
|
621
|
+
|
|
622
|
+
it 'creates sorts based on a single alias/direction' do
|
|
623
|
+
@s.sorts = 'daddy desc'
|
|
624
|
+
expect(@s.sorts.size).to eq(1)
|
|
625
|
+
sort = @s.sorts.first
|
|
626
|
+
expect(sort).to be_a Nodes::Sort
|
|
627
|
+
expect(sort.name).to eq 'parent_name'
|
|
628
|
+
expect(sort.dir).to eq 'desc'
|
|
629
|
+
end
|
|
630
|
+
|
|
631
|
+
it 'creates sorts based on a single alias and uppercase direction' do
|
|
632
|
+
@s.sorts = 'daddy DESC'
|
|
633
|
+
expect(@s.sorts.size).to eq(1)
|
|
634
|
+
sort = @s.sorts.first
|
|
635
|
+
expect(sort).to be_a Nodes::Sort
|
|
636
|
+
expect(sort.name).to eq 'parent_name'
|
|
637
|
+
expect(sort.dir).to eq 'desc'
|
|
638
|
+
end
|
|
639
|
+
|
|
640
|
+
it 'creates sorts based on a single alias and without direction' do
|
|
641
|
+
@s.sorts = 'daddy'
|
|
642
|
+
expect(@s.sorts.size).to eq(1)
|
|
643
|
+
sort = @s.sorts.first
|
|
644
|
+
expect(sort).to be_a Nodes::Sort
|
|
645
|
+
expect(sort.name).to eq 'parent_name'
|
|
646
|
+
expect(sort.dir).to eq 'asc'
|
|
647
|
+
end
|
|
648
|
+
|
|
649
|
+
it 'creates sorts based on attributes, alias and directions in array format' do
|
|
650
|
+
@s.sorts = ['id desc', { name: 'daddy', dir: 'asc' }]
|
|
651
|
+
expect(@s.sorts.size).to eq(2)
|
|
652
|
+
sort1, sort2 = @s.sorts
|
|
653
|
+
expect(sort1).to be_a Nodes::Sort
|
|
654
|
+
expect(sort1.name).to eq 'id'
|
|
655
|
+
expect(sort1.dir).to eq 'desc'
|
|
656
|
+
expect(sort2).to be_a Nodes::Sort
|
|
657
|
+
expect(sort2.name).to eq 'parent_name'
|
|
658
|
+
expect(sort2.dir).to eq 'asc'
|
|
659
|
+
end
|
|
660
|
+
|
|
661
|
+
it 'creates sorts based on attributes, alias and uppercase directions in array format' do
|
|
662
|
+
@s.sorts = ['id DESC', { name: 'daddy', dir: 'ASC' }]
|
|
663
|
+
expect(@s.sorts.size).to eq(2)
|
|
664
|
+
sort1, sort2 = @s.sorts
|
|
665
|
+
expect(sort1).to be_a Nodes::Sort
|
|
666
|
+
expect(sort1.name).to eq 'id'
|
|
667
|
+
expect(sort1.dir).to eq 'desc'
|
|
668
|
+
expect(sort2).to be_a Nodes::Sort
|
|
669
|
+
expect(sort2.name).to eq 'parent_name'
|
|
670
|
+
expect(sort2.dir).to eq 'asc'
|
|
671
|
+
end
|
|
672
|
+
|
|
673
|
+
it 'creates sorts based on attributes, alias and different directions
|
|
674
|
+
in array format' do
|
|
675
|
+
@s.sorts = ['id DESC', { name: 'daddy', dir: nil }]
|
|
676
|
+
expect(@s.sorts.size).to eq(2)
|
|
677
|
+
sort1, sort2 = @s.sorts
|
|
678
|
+
expect(sort1).to be_a Nodes::Sort
|
|
679
|
+
expect(sort1.name).to eq 'id'
|
|
680
|
+
expect(sort1.dir).to eq 'desc'
|
|
681
|
+
expect(sort2).to be_a Nodes::Sort
|
|
682
|
+
expect(sort2.name).to eq 'parent_name'
|
|
683
|
+
expect(sort2.dir).to eq 'asc'
|
|
684
|
+
end
|
|
685
|
+
|
|
686
|
+
it 'creates sorts based on attributes, alias and directions in hash format' do
|
|
687
|
+
@s.sorts = {
|
|
688
|
+
'0' => { name: 'id', dir: 'desc' },
|
|
689
|
+
'1' => { name: 'daddy', dir: 'asc' }
|
|
690
|
+
}
|
|
691
|
+
expect(@s.sorts.size).to eq(2)
|
|
692
|
+
expect(@s.sorts).to be_all { |s| Nodes::Sort === s }
|
|
693
|
+
id_sort = @s.sorts.detect { |s| s.name == 'id' }
|
|
694
|
+
daddy_sort = @s.sorts.detect { |s| s.name == 'parent_name' }
|
|
695
|
+
expect(id_sort.dir).to eq 'desc'
|
|
696
|
+
expect(daddy_sort.dir).to eq 'asc'
|
|
697
|
+
end
|
|
698
|
+
|
|
699
|
+
it 'creates sorts based on attributes, alias and uppercase directions
|
|
700
|
+
in hash format' do
|
|
701
|
+
@s.sorts = {
|
|
702
|
+
'0' => { name: 'id', dir: 'DESC' },
|
|
703
|
+
'1' => { name: 'daddy', dir: 'ASC' }
|
|
704
|
+
}
|
|
705
|
+
expect(@s.sorts.size).to eq(2)
|
|
706
|
+
expect(@s.sorts).to be_all { |s| Nodes::Sort === s }
|
|
707
|
+
id_sort = @s.sorts.detect { |s| s.name == 'id' }
|
|
708
|
+
daddy_sort = @s.sorts.detect { |s| s.name == 'parent_name' }
|
|
709
|
+
expect(id_sort.dir).to eq 'desc'
|
|
710
|
+
expect(daddy_sort.dir).to eq 'asc'
|
|
711
|
+
end
|
|
712
|
+
|
|
713
|
+
it 'creates sorts based on attributes, alias and different directions
|
|
714
|
+
in hash format' do
|
|
715
|
+
@s.sorts = {
|
|
716
|
+
'0' => { name: 'id', dir: 'DESC' },
|
|
717
|
+
'1' => { name: 'daddy', dir: nil }
|
|
718
|
+
}
|
|
719
|
+
expect(@s.sorts.size).to eq(2)
|
|
720
|
+
expect(@s.sorts).to be_all { |s| Nodes::Sort === s }
|
|
721
|
+
id_sort = @s.sorts.detect { |s| s.name == 'id' }
|
|
722
|
+
daddy_sort = @s.sorts.detect { |s| s.name == 'parent_name' }
|
|
723
|
+
expect(id_sort.dir).to eq 'desc'
|
|
724
|
+
expect(daddy_sort.dir).to eq 'asc'
|
|
725
|
+
end
|
|
726
|
+
|
|
727
|
+
it 'overrides existing sort' do
|
|
728
|
+
@s.sorts = 'id asc'
|
|
729
|
+
expect(@s.result.first.id).to eq 1
|
|
730
|
+
end
|
|
731
|
+
|
|
732
|
+
it 'raises ArgumentError when an invalid argument is sent' do
|
|
733
|
+
expect do
|
|
734
|
+
@s.sorts = 1234
|
|
735
|
+
end.to raise_error(ArgumentError, "Invalid argument (Integer) supplied to sorts=")
|
|
736
|
+
end
|
|
737
|
+
|
|
738
|
+
it 'raises InvalidSearchError when an invalid argument is sent' do
|
|
739
|
+
expect do
|
|
740
|
+
@s.sorts = 1234
|
|
741
|
+
end.to raise_error(Ransack::InvalidSearchError, "Invalid argument (Integer) supplied to sorts=")
|
|
742
|
+
end
|
|
743
|
+
|
|
744
|
+
it "PG's sort option", if: ::ActiveRecord::Base.connection.adapter_name == "PostgreSQL" do
|
|
745
|
+
default = Ransack.options.clone
|
|
746
|
+
|
|
747
|
+
s = Search.new(Person, s: 'name asc')
|
|
748
|
+
expect(s.result.to_sql).to eq "SELECT \"people\".* FROM \"people\" ORDER BY \"people\".\"name\" ASC"
|
|
749
|
+
|
|
750
|
+
Ransack.configure { |c| c.postgres_fields_sort_option = :nulls_first }
|
|
751
|
+
s = Search.new(Person, s: 'name asc')
|
|
752
|
+
expect(s.result.to_sql).to eq "SELECT \"people\".* FROM \"people\" ORDER BY \"people\".\"name\" ASC NULLS FIRST"
|
|
753
|
+
s = Search.new(Person, s: 'name desc')
|
|
754
|
+
expect(s.result.to_sql).to eq "SELECT \"people\".* FROM \"people\" ORDER BY \"people\".\"name\" DESC NULLS LAST"
|
|
755
|
+
|
|
756
|
+
Ransack.configure { |c| c.postgres_fields_sort_option = :nulls_last }
|
|
757
|
+
s = Search.new(Person, s: 'name asc')
|
|
758
|
+
expect(s.result.to_sql).to eq "SELECT \"people\".* FROM \"people\" ORDER BY \"people\".\"name\" ASC NULLS LAST"
|
|
759
|
+
s = Search.new(Person, s: 'name desc')
|
|
760
|
+
expect(s.result.to_sql).to eq "SELECT \"people\".* FROM \"people\" ORDER BY \"people\".\"name\" DESC NULLS FIRST"
|
|
761
|
+
|
|
762
|
+
Ransack.options = default
|
|
763
|
+
end
|
|
764
|
+
|
|
765
|
+
it "PG's sort option with double name", if: ::ActiveRecord::Base.connection.adapter_name == "PostgreSQL" do
|
|
766
|
+
default = Ransack.options.clone
|
|
767
|
+
|
|
768
|
+
s = Search.new(Person, s: 'doubled_name asc')
|
|
769
|
+
expect(s.result.to_sql).to eq "SELECT \"people\".* FROM \"people\" ORDER BY \"people\".\"name\" || \"people\".\"name\" ASC"
|
|
770
|
+
|
|
771
|
+
Ransack.configure { |c| c.postgres_fields_sort_option = :nulls_first }
|
|
772
|
+
s = Search.new(Person, s: 'doubled_name asc')
|
|
773
|
+
expect(s.result.to_sql).to eq "SELECT \"people\".* FROM \"people\" ORDER BY \"people\".\"name\" || \"people\".\"name\" ASC NULLS FIRST"
|
|
774
|
+
s = Search.new(Person, s: 'doubled_name desc')
|
|
775
|
+
expect(s.result.to_sql).to eq "SELECT \"people\".* FROM \"people\" ORDER BY \"people\".\"name\" || \"people\".\"name\" DESC NULLS LAST"
|
|
776
|
+
|
|
777
|
+
Ransack.configure { |c| c.postgres_fields_sort_option = :nulls_last }
|
|
778
|
+
s = Search.new(Person, s: 'doubled_name asc')
|
|
779
|
+
expect(s.result.to_sql).to eq "SELECT \"people\".* FROM \"people\" ORDER BY \"people\".\"name\" || \"people\".\"name\" ASC NULLS LAST"
|
|
780
|
+
s = Search.new(Person, s: 'doubled_name desc')
|
|
781
|
+
expect(s.result.to_sql).to eq "SELECT \"people\".* FROM \"people\" ORDER BY \"people\".\"name\" || \"people\".\"name\" DESC NULLS FIRST"
|
|
782
|
+
|
|
783
|
+
Ransack.configure { |c| c.postgres_fields_sort_option = :nulls_always_first }
|
|
784
|
+
s = Search.new(Person, s: 'doubled_name asc')
|
|
785
|
+
expect(s.result.to_sql).to eq "SELECT \"people\".* FROM \"people\" ORDER BY \"people\".\"name\" || \"people\".\"name\" ASC NULLS FIRST"
|
|
786
|
+
s = Search.new(Person, s: 'doubled_name desc')
|
|
787
|
+
expect(s.result.to_sql).to eq "SELECT \"people\".* FROM \"people\" ORDER BY \"people\".\"name\" || \"people\".\"name\" DESC NULLS FIRST"
|
|
788
|
+
|
|
789
|
+
Ransack.configure { |c| c.postgres_fields_sort_option = :nulls_always_last }
|
|
790
|
+
s = Search.new(Person, s: 'doubled_name asc')
|
|
791
|
+
expect(s.result.to_sql).to eq "SELECT \"people\".* FROM \"people\" ORDER BY \"people\".\"name\" || \"people\".\"name\" ASC NULLS LAST"
|
|
792
|
+
s = Search.new(Person, s: 'doubled_name desc')
|
|
793
|
+
expect(s.result.to_sql).to eq "SELECT \"people\".* FROM \"people\" ORDER BY \"people\".\"name\" || \"people\".\"name\" DESC NULLS LAST"
|
|
794
|
+
|
|
795
|
+
Ransack.options = default
|
|
796
|
+
end
|
|
797
|
+
end
|
|
798
|
+
|
|
799
|
+
describe '#method_missing' do
|
|
800
|
+
before do
|
|
801
|
+
@s = Search.new(Person)
|
|
802
|
+
end
|
|
803
|
+
|
|
804
|
+
it 'raises NoMethodError when sent an invalid attribute' do
|
|
805
|
+
expect { @s.blah }.to raise_error NoMethodError
|
|
806
|
+
end
|
|
807
|
+
|
|
808
|
+
it 'sets condition attributes when sent valid attributes' do
|
|
809
|
+
@s.name_eq = 'Ernie'
|
|
810
|
+
expect(@s.name_eq).to eq 'Ernie'
|
|
811
|
+
end
|
|
812
|
+
|
|
813
|
+
it 'allows chaining to access nested conditions' do
|
|
814
|
+
@s.groupings = [
|
|
815
|
+
{ m: 'or', name_eq: 'Ernie', children_name_eq: 'Ernie' }
|
|
816
|
+
]
|
|
817
|
+
expect(@s.groupings.first.children_name_eq).to eq 'Ernie'
|
|
818
|
+
end
|
|
819
|
+
end
|
|
820
|
+
end
|
|
821
|
+
end
|