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,943 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
module Ransack
|
|
4
|
+
module Adapters
|
|
5
|
+
module ActiveRecord
|
|
6
|
+
describe Base do
|
|
7
|
+
subject { ::ActiveRecord::Base }
|
|
8
|
+
|
|
9
|
+
it { should respond_to :ransack }
|
|
10
|
+
|
|
11
|
+
describe '#search' do
|
|
12
|
+
subject { Person.ransack }
|
|
13
|
+
|
|
14
|
+
it { should be_a Search }
|
|
15
|
+
it 'has a Relation as its object' do
|
|
16
|
+
expect(subject.object).to be_an ::ActiveRecord::Relation
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
context "multiple database connection" do
|
|
20
|
+
it "does not raise error" do
|
|
21
|
+
expect { Person.ransack(name_cont: "test") }.not_to raise_error
|
|
22
|
+
expect { SubDB::OperationHistory.ransack(people_id_eq: 1) }.not_to raise_error
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
context 'with scopes' do
|
|
27
|
+
before do
|
|
28
|
+
allow(Person)
|
|
29
|
+
.to receive(:ransackable_scopes)
|
|
30
|
+
.and_return([:active, :over_age, :of_age])
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
it 'applies true scopes' do
|
|
34
|
+
s = Person.ransack('active' => true)
|
|
35
|
+
expect(s.result.to_sql).to (include 'active = 1')
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
it 'applies stringy true scopes' do
|
|
39
|
+
s = Person.ransack('active' => 'true')
|
|
40
|
+
expect(s.result.to_sql).to (include 'active = 1')
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
it 'applies stringy boolean scopes with true value in an array' do
|
|
44
|
+
s = Person.ransack('of_age' => ['true'])
|
|
45
|
+
expect(s.result.to_sql).to (include rails7_and_mysql ? %q{(age >= '18')} : 'age >= 18')
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
it 'applies stringy boolean scopes with false value in an array' do
|
|
49
|
+
s = Person.ransack('of_age' => ['false'])
|
|
50
|
+
expect(s.result.to_sql).to (include rails7_and_mysql ? %q{age < '18'} : 'age < 18')
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
it 'ignores unlisted scopes' do
|
|
54
|
+
s = Person.ransack('restricted' => true)
|
|
55
|
+
expect(s.result.to_sql).to_not (include 'restricted')
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
it 'ignores false scopes' do
|
|
59
|
+
s = Person.ransack('active' => false)
|
|
60
|
+
expect(s.result.to_sql).not_to (include 'active')
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
it 'ignores stringy false scopes' do
|
|
64
|
+
s = Person.ransack('active' => 'false')
|
|
65
|
+
expect(s.result.to_sql).to_not (include 'active')
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
it 'passes values to scopes' do
|
|
69
|
+
s = Person.ransack('over_age' => 18)
|
|
70
|
+
expect(s.result.to_sql).to (include rails7_and_mysql ? %q{age > '18'} : 'age > 18')
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
it 'chains scopes' do
|
|
74
|
+
s = Person.ransack('over_age' => 18, 'active' => true)
|
|
75
|
+
expect(s.result.to_sql).to (include rails7_and_mysql ? %q{age > '18'} : 'age > 18')
|
|
76
|
+
expect(s.result.to_sql).to (include 'active = 1')
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
it 'applies scopes that define string SQL joins' do
|
|
80
|
+
allow(Article)
|
|
81
|
+
.to receive(:ransackable_scopes)
|
|
82
|
+
.and_return([:latest_comment_cont])
|
|
83
|
+
|
|
84
|
+
# Including a negative condition to test removing the scope
|
|
85
|
+
s = Search.new(Article, notes_note_not_eq: 'Test', latest_comment_cont: 'Test')
|
|
86
|
+
expect(s.result.to_sql).to include 'latest_comment'
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
context "with sanitize_custom_scope_booleans set to false" do
|
|
90
|
+
before(:all) do
|
|
91
|
+
Ransack.configure { |c| c.sanitize_custom_scope_booleans = false }
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
after(:all) do
|
|
95
|
+
Ransack.configure { |c| c.sanitize_custom_scope_booleans = true }
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
it 'passes true values to scopes' do
|
|
99
|
+
s = Person.ransack('over_age' => 1)
|
|
100
|
+
expect(s.result.to_sql).to (include rails7_and_mysql ? %q{age > '1'} : 'age > 1')
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
it 'passes false values to scopes' do
|
|
104
|
+
s = Person.ransack('over_age' => 0)
|
|
105
|
+
expect(s.result.to_sql).to (include rails7_and_mysql ? %q{age > '0'} : 'age > 0')
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
context "with ransackable_scopes_skip_sanitize_args enabled for scope" do
|
|
110
|
+
before do
|
|
111
|
+
allow(Person)
|
|
112
|
+
.to receive(:ransackable_scopes_skip_sanitize_args)
|
|
113
|
+
.and_return([:over_age])
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
it 'passes true values to scopes' do
|
|
117
|
+
s = Person.ransack('over_age' => 1)
|
|
118
|
+
expect(s.result.to_sql).to (include rails7_and_mysql ? %q{age > '1'} : 'age > 1')
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
it 'passes false values to scopes' do
|
|
122
|
+
s = Person.ransack('over_age' => 0)
|
|
123
|
+
expect(s.result.to_sql).to (include rails7_and_mysql ? %q{age > '0'} : 'age > 0')
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
it 'does not raise exception for string :params argument' do
|
|
129
|
+
expect { Person.ransack('') }.to_not raise_error
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
it 'raises ArgumentError exception if ransack! called with unknown condition' do
|
|
133
|
+
expect { Person.ransack!(unknown_attr_eq: 'Ernie') }.to raise_error(ArgumentError)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
it 'raises InvalidSearchError exception if ransack! called with unknown condition' do
|
|
137
|
+
expect { Person.ransack!(unknown_attr_eq: 'Ernie') }.to raise_error(InvalidSearchError)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
it 'does not modify the parameters' do
|
|
141
|
+
params = { name_eq: '' }
|
|
142
|
+
expect { Person.ransack(params) }.not_to change { params }
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
context 'has_one through associations' do
|
|
147
|
+
let(:address) { Address.create!(city: 'Boston') }
|
|
148
|
+
let(:org) { Organization.create!(name: 'Testorg', address: address) }
|
|
149
|
+
let!(:employee) { Employee.create!(name: 'Ernie', organization: org) }
|
|
150
|
+
|
|
151
|
+
it 'works when has_one through association is first' do
|
|
152
|
+
s = Employee.ransack(address_city_eq: 'Boston', organization_name_eq: 'Testorg')
|
|
153
|
+
expect(s.result.to_a).to include(employee)
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
it 'works when has_one through association is last' do
|
|
157
|
+
s = Employee.ransack(organization_name_eq: 'Testorg', address_city_eq: 'Boston')
|
|
158
|
+
expect(s.result.to_a).to include(employee)
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
context 'negative conditions on HABTM associations' do
|
|
163
|
+
let(:medieval) { Tag.create!(name: 'Medieval') }
|
|
164
|
+
let(:fantasy) { Tag.create!(name: 'Fantasy') }
|
|
165
|
+
let(:arthur) { Article.create!(title: 'King Arthur') }
|
|
166
|
+
let(:marco) { Article.create!(title: 'Marco Polo') }
|
|
167
|
+
|
|
168
|
+
before do
|
|
169
|
+
marco.tags << medieval
|
|
170
|
+
arthur.tags << medieval
|
|
171
|
+
arthur.tags << fantasy
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
it 'removes redundant joins from top query' do
|
|
175
|
+
s = Article.ransack(tags_name_not_eq: "Fantasy")
|
|
176
|
+
sql = s.result.to_sql
|
|
177
|
+
expect(sql).to_not include('LEFT OUTER JOIN')
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
it 'handles != for single values' do
|
|
181
|
+
s = Article.ransack(tags_name_not_eq: "Fantasy")
|
|
182
|
+
articles = s.result.to_a
|
|
183
|
+
expect(articles).to include marco
|
|
184
|
+
expect(articles).to_not include arthur
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
it 'handles NOT IN for multiple attributes' do
|
|
188
|
+
s = Article.ransack(tags_name_not_in: ["Fantasy", "Scifi"])
|
|
189
|
+
articles = s.result.to_a
|
|
190
|
+
|
|
191
|
+
expect(articles).to include marco
|
|
192
|
+
expect(articles).to_not include arthur
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
context 'negative conditions on related object with HABTM associations' do
|
|
197
|
+
let(:medieval) { Tag.create!(name: 'Medieval') }
|
|
198
|
+
let(:fantasy) { Tag.create!(name: 'Fantasy') }
|
|
199
|
+
let(:arthur) { Article.create!(title: 'King Arthur') }
|
|
200
|
+
let(:marco) { Article.create!(title: 'Marco Polo') }
|
|
201
|
+
let(:comment_arthur) { marco.comments.create!(body: 'King Arthur comment') }
|
|
202
|
+
let(:comment_marco) { arthur.comments.create!(body: 'Marco Polo comment') }
|
|
203
|
+
|
|
204
|
+
before do
|
|
205
|
+
comment_arthur.tags << medieval
|
|
206
|
+
comment_marco.tags << fantasy
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
it 'removes redundant joins from top query' do
|
|
210
|
+
s = Article.ransack(comments_tags_name_not_eq: "Fantasy")
|
|
211
|
+
sql = s.result.to_sql
|
|
212
|
+
expect(sql).to include('LEFT OUTER JOIN')
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
it 'handles != for single values' do
|
|
216
|
+
s = Article.ransack(comments_tags_name_not_eq: "Fantasy")
|
|
217
|
+
articles = s.result.to_a
|
|
218
|
+
expect(articles).to include marco
|
|
219
|
+
expect(articles).to_not include arthur
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
it 'handles NOT IN for multiple attributes' do
|
|
223
|
+
s = Article.ransack(comments_tags_name_not_in: ["Fantasy", "Scifi"])
|
|
224
|
+
articles = s.result.to_a
|
|
225
|
+
|
|
226
|
+
expect(articles).to include marco
|
|
227
|
+
expect(articles).to_not include arthur
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
context 'negative conditions on self-referenced associations' do
|
|
232
|
+
let(:pop) { Person.create!(name: 'Grandpa') }
|
|
233
|
+
let(:dad) { Person.create!(name: 'Father') }
|
|
234
|
+
let(:mom) { Person.create!(name: 'Mother') }
|
|
235
|
+
let(:son) { Person.create!(name: 'Grandchild') }
|
|
236
|
+
|
|
237
|
+
before do
|
|
238
|
+
son.parent = dad
|
|
239
|
+
dad.parent = pop
|
|
240
|
+
dad.children << son
|
|
241
|
+
mom.children << son
|
|
242
|
+
pop.children << dad
|
|
243
|
+
son.save! && dad.save! && mom.save! && pop.save!
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
it 'handles multiple associations and aliases' do
|
|
247
|
+
s = Person.ransack(
|
|
248
|
+
c: {
|
|
249
|
+
'0' => { a: ['name'], p: 'not_eq', v: ['Father'] },
|
|
250
|
+
'1' => {
|
|
251
|
+
a: ['children_name', 'parent_name'],
|
|
252
|
+
p: 'not_eq', v: ['Father'], m: 'or'
|
|
253
|
+
},
|
|
254
|
+
'2' => { a: ['children_salary'], p: 'eq', v: [nil] }
|
|
255
|
+
})
|
|
256
|
+
people = s.result
|
|
257
|
+
|
|
258
|
+
expect(people.to_a).to include son
|
|
259
|
+
expect(people.to_a).to include mom
|
|
260
|
+
expect(people.to_a).to_not include dad # rule '0': 'name'
|
|
261
|
+
expect(people.to_a).to_not include pop # rule '1': 'children_name'
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
describe '#ransack_alias' do
|
|
266
|
+
it 'translates an alias to the correct attributes' do
|
|
267
|
+
p = Person.create!(name: 'Meatloaf', email: 'babies@example.com')
|
|
268
|
+
|
|
269
|
+
s = Person.ransack(term_cont: 'atlo')
|
|
270
|
+
expect(s.result.to_a).to eq [p]
|
|
271
|
+
|
|
272
|
+
s = Person.ransack(term_cont: 'babi')
|
|
273
|
+
expect(s.result.to_a).to eq [p]
|
|
274
|
+
|
|
275
|
+
s = Person.ransack(term_cont: 'nomatch')
|
|
276
|
+
expect(s.result.to_a).to eq []
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
it 'also works with associations' do
|
|
280
|
+
dad = Person.create!(name: 'Birdman')
|
|
281
|
+
son = Person.create!(name: 'Weezy', parent: dad)
|
|
282
|
+
|
|
283
|
+
s = Person.ransack(daddy_eq: 'Birdman')
|
|
284
|
+
expect(s.result.to_a).to eq [son]
|
|
285
|
+
|
|
286
|
+
s = Person.ransack(daddy_eq: 'Drake')
|
|
287
|
+
expect(s.result.to_a).to eq []
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
it 'makes aliases available to subclasses' do
|
|
291
|
+
yngwie = Musician.create!(name: 'Yngwie Malmsteen')
|
|
292
|
+
|
|
293
|
+
musicians = Musician.ransack(term_cont: 'ngw').result
|
|
294
|
+
expect(musicians).to eq([yngwie])
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
it 'handles naming collisions gracefully' do
|
|
298
|
+
frank = Person.create!(name: 'Frank Stallone')
|
|
299
|
+
|
|
300
|
+
people = Person.ransack(term_cont: 'allon').result
|
|
301
|
+
expect(people).to eq([frank])
|
|
302
|
+
|
|
303
|
+
Class.new(Article) do
|
|
304
|
+
ransack_alias :term, :title
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
people = Person.ransack(term_cont: 'allon').result
|
|
308
|
+
expect(people).to eq([frank])
|
|
309
|
+
end
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
describe '#ransacker' do
|
|
313
|
+
# For infix tests
|
|
314
|
+
def self.sane_adapter?
|
|
315
|
+
case ::ActiveRecord::Base.connection.adapter_name
|
|
316
|
+
when 'SQLite3', 'PostgreSQL'
|
|
317
|
+
true
|
|
318
|
+
else
|
|
319
|
+
false
|
|
320
|
+
end
|
|
321
|
+
end
|
|
322
|
+
# in schema.rb, class Person:
|
|
323
|
+
# ransacker :reversed_name, formatter: proc { |v| v.reverse } do |parent|
|
|
324
|
+
# parent.table[:name]
|
|
325
|
+
# end
|
|
326
|
+
#
|
|
327
|
+
# ransacker :doubled_name do |parent|
|
|
328
|
+
# Arel::Nodes::InfixOperation.new(
|
|
329
|
+
# '||', parent.table[:name], parent.table[:name]
|
|
330
|
+
# )
|
|
331
|
+
# end
|
|
332
|
+
|
|
333
|
+
it 'creates ransack attributes' do
|
|
334
|
+
person = Person.create!(name: 'Aric Smith')
|
|
335
|
+
|
|
336
|
+
s = Person.ransack(reversed_name_eq: 'htimS cirA')
|
|
337
|
+
expect(s.result.size).to eq(1)
|
|
338
|
+
|
|
339
|
+
expect(s.result.first).to eq person
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
it 'can be accessed through associations' do
|
|
343
|
+
s = Person.ransack(children_reversed_name_eq: 'htimS cirA')
|
|
344
|
+
expect(s.result.to_sql).to match(
|
|
345
|
+
/#{quote_table_name("children_people")}.#{
|
|
346
|
+
quote_column_name("name")} = 'Aric Smith'/
|
|
347
|
+
)
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
it 'allows an attribute to be an InfixOperation' do
|
|
351
|
+
s = Person.ransack(doubled_name_eq: 'Aric SmithAric Smith')
|
|
352
|
+
expect(s.result.first).to eq Person.where(name: 'Aric Smith').first
|
|
353
|
+
end if defined?(Arel::Nodes::InfixOperation) && sane_adapter?
|
|
354
|
+
|
|
355
|
+
it 'does not break #count if using InfixOperations' do
|
|
356
|
+
s = Person.ransack(doubled_name_eq: 'Aric SmithAric Smith')
|
|
357
|
+
expect(s.result.count).to eq 1
|
|
358
|
+
end if defined?(Arel::Nodes::InfixOperation) && sane_adapter?
|
|
359
|
+
|
|
360
|
+
it 'should remove empty key value pairs from the params hash' do
|
|
361
|
+
s = Person.ransack(children_reversed_name_eq: '')
|
|
362
|
+
expect(s.result.to_sql).not_to match /LEFT OUTER JOIN/
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
it 'should keep proper key value pairs in the params hash' do
|
|
366
|
+
s = Person.ransack(children_reversed_name_eq: 'Testing')
|
|
367
|
+
expect(s.result.to_sql).to match /LEFT OUTER JOIN/
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
it 'should function correctly when nil is passed in' do
|
|
371
|
+
s = Person.ransack(nil)
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
it 'should function correctly when a blank string is passed in' do
|
|
375
|
+
s = Person.ransack('')
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
it 'should function correctly with a multi-parameter attribute' do
|
|
379
|
+
if ::ActiveRecord::VERSION::MAJOR >= 7
|
|
380
|
+
::ActiveRecord.default_timezone = :utc
|
|
381
|
+
else
|
|
382
|
+
::ActiveRecord::Base.default_timezone = :utc
|
|
383
|
+
end
|
|
384
|
+
Time.zone = 'UTC'
|
|
385
|
+
|
|
386
|
+
date = Date.current
|
|
387
|
+
s = Person.ransack(
|
|
388
|
+
{ 'created_at_gteq(1i)' => date.year,
|
|
389
|
+
'created_at_gteq(2i)' => date.month,
|
|
390
|
+
'created_at_gteq(3i)' => date.day
|
|
391
|
+
}
|
|
392
|
+
)
|
|
393
|
+
expect(s.result.to_sql).to match />=/
|
|
394
|
+
expect(s.result.to_sql).to match date.to_s
|
|
395
|
+
end
|
|
396
|
+
|
|
397
|
+
it 'should function correctly when using fields with dots in them' do
|
|
398
|
+
s = Person.ransack(email_cont: 'example.com')
|
|
399
|
+
expect(s.result.exists?).to be true
|
|
400
|
+
end
|
|
401
|
+
|
|
402
|
+
it 'should function correctly when using fields with % in them' do
|
|
403
|
+
p = Person.create!(name: '110%-er')
|
|
404
|
+
s = Person.ransack(name_cont: '10%')
|
|
405
|
+
expect(s.result.to_a).to eq [p]
|
|
406
|
+
end
|
|
407
|
+
|
|
408
|
+
it 'should function correctly when using fields with backslashes in them' do
|
|
409
|
+
p = Person.create!(name: "\\WINNER\\")
|
|
410
|
+
s = Person.ransack(name_cont: "\\WINNER\\")
|
|
411
|
+
expect(s.result.to_a).to eq [p]
|
|
412
|
+
end
|
|
413
|
+
|
|
414
|
+
if ::ActiveRecord::VERSION::MAJOR >= 7 && ActiveRecord::Base.respond_to?(:normalizes)
|
|
415
|
+
context 'with ActiveRecord::normalizes' do
|
|
416
|
+
around(:each) do |example|
|
|
417
|
+
# Create a temporary model class with normalization for testing
|
|
418
|
+
test_class = Class.new(ActiveRecord::Base) do
|
|
419
|
+
self.table_name = 'people'
|
|
420
|
+
normalizes :name, with: ->(name) { name.gsub(/[^a-z0-9]/, '_') }
|
|
421
|
+
|
|
422
|
+
def self.ransackable_attributes(auth_object = nil)
|
|
423
|
+
Person.ransackable_attributes(auth_object)
|
|
424
|
+
end
|
|
425
|
+
|
|
426
|
+
def self.name
|
|
427
|
+
'TestPersonWithNormalization'
|
|
428
|
+
end
|
|
429
|
+
end
|
|
430
|
+
|
|
431
|
+
stub_const('TestPersonWithNormalization', test_class)
|
|
432
|
+
example.run
|
|
433
|
+
end
|
|
434
|
+
|
|
435
|
+
it 'should not apply normalization to LIKE wildcards for cont predicate' do
|
|
436
|
+
# Create a person with characters that would be normalized
|
|
437
|
+
p = TestPersonWithNormalization.create!(name: 'foo%bar')
|
|
438
|
+
expect(p.reload.name).to eq('foo_bar') # Verify normalization happened on storage
|
|
439
|
+
|
|
440
|
+
# Search should find the person using the original search term
|
|
441
|
+
s = TestPersonWithNormalization.ransack(name_cont: 'foo')
|
|
442
|
+
expect(s.result.to_a).to eq [p]
|
|
443
|
+
|
|
444
|
+
# Verify the SQL contains proper LIKE wildcards, not normalized ones
|
|
445
|
+
sql = s.result.to_sql
|
|
446
|
+
expect(sql).to include("LIKE '%foo%'")
|
|
447
|
+
expect(sql).not_to include("LIKE '_foo_'")
|
|
448
|
+
end
|
|
449
|
+
|
|
450
|
+
it 'should not apply normalization to LIKE wildcards for other LIKE predicates' do
|
|
451
|
+
p = TestPersonWithNormalization.create!(name: 'foo%bar')
|
|
452
|
+
|
|
453
|
+
# Test start predicate
|
|
454
|
+
s = TestPersonWithNormalization.ransack(name_start: 'foo')
|
|
455
|
+
expect(s.result.to_a).to eq [p]
|
|
456
|
+
expect(s.result.to_sql).to include("LIKE 'foo%'")
|
|
457
|
+
|
|
458
|
+
# Test end predicate
|
|
459
|
+
s = TestPersonWithNormalization.ransack(name_end: 'bar')
|
|
460
|
+
expect(s.result.to_a).to eq [p]
|
|
461
|
+
expect(s.result.to_sql).to include("LIKE '%bar'")
|
|
462
|
+
|
|
463
|
+
# Test i_cont predicate
|
|
464
|
+
s = TestPersonWithNormalization.ransack(name_i_cont: 'FOO')
|
|
465
|
+
expect(s.result.to_a).to eq [p]
|
|
466
|
+
expect(s.result.to_sql).to include("LIKE '%foo%'")
|
|
467
|
+
end
|
|
468
|
+
end
|
|
469
|
+
end
|
|
470
|
+
|
|
471
|
+
context 'searching by underscores' do
|
|
472
|
+
# when escaping is supported right in LIKE expression without adding extra expressions
|
|
473
|
+
def self.simple_escaping?
|
|
474
|
+
case ::ActiveRecord::Base.connection.adapter_name
|
|
475
|
+
when 'Mysql2', 'PostgreSQL'
|
|
476
|
+
true
|
|
477
|
+
else
|
|
478
|
+
false
|
|
479
|
+
end
|
|
480
|
+
end
|
|
481
|
+
|
|
482
|
+
it 'should search correctly if matches exist' do
|
|
483
|
+
p = Person.create!(name: 'name_with_underscore')
|
|
484
|
+
s = Person.ransack(name_cont: 'name_')
|
|
485
|
+
expect(s.result.to_a).to eq [p]
|
|
486
|
+
end if simple_escaping?
|
|
487
|
+
|
|
488
|
+
it 'should return empty result if no matches' do
|
|
489
|
+
Person.create!(name: 'name_with_underscore')
|
|
490
|
+
s = Person.ransack(name_cont: 'n_')
|
|
491
|
+
expect(s.result.to_a).to eq []
|
|
492
|
+
end if simple_escaping?
|
|
493
|
+
end
|
|
494
|
+
|
|
495
|
+
context 'searching on an `in` predicate with a ransacker' do
|
|
496
|
+
it 'should function correctly when passing an array of ids' do
|
|
497
|
+
s = Person.ransack(array_people_ids_in: true)
|
|
498
|
+
expect(s.result.count).to be > 0
|
|
499
|
+
|
|
500
|
+
s = Person.ransack(array_where_people_ids_in: [1, '2', 3])
|
|
501
|
+
expect(s.result.count).to be 3
|
|
502
|
+
expect(s.result.map(&:id)).to eq [3, 2, 1]
|
|
503
|
+
end
|
|
504
|
+
|
|
505
|
+
it 'should function correctly with HABTM associations' do
|
|
506
|
+
article = Article.first
|
|
507
|
+
tag = article.tags.first
|
|
508
|
+
s = Person.ransack(article_tags_in: [tag.id])
|
|
509
|
+
|
|
510
|
+
expect(s.result.count).to be 1
|
|
511
|
+
expect(s.result.map(&:id)).to eq [article.person.id]
|
|
512
|
+
end
|
|
513
|
+
|
|
514
|
+
it 'should function correctly when passing an array of strings' do
|
|
515
|
+
a, b = Person.select(:id).order(:id).limit(2).map { |a| a.id.to_s }
|
|
516
|
+
|
|
517
|
+
Person.create!(name: a)
|
|
518
|
+
s = Person.ransack(array_people_names_in: true)
|
|
519
|
+
expect(s.result.count).to be > 0
|
|
520
|
+
s = Person.ransack(array_where_people_names_in: a)
|
|
521
|
+
expect(s.result.count).to be 1
|
|
522
|
+
|
|
523
|
+
Person.create!(name: b)
|
|
524
|
+
s = Person.ransack(array_where_people_names_in: [a, b])
|
|
525
|
+
expect(s.result.count).to be 2
|
|
526
|
+
end
|
|
527
|
+
|
|
528
|
+
it 'should function correctly with an Arel SqlLiteral' do
|
|
529
|
+
s = Person.ransack(sql_literal_id_in: 1)
|
|
530
|
+
expect(s.result.count).to be 1
|
|
531
|
+
s = Person.ransack(sql_literal_id_in: ['2', 4, '5', 8])
|
|
532
|
+
expect(s.result.count).to be 4
|
|
533
|
+
end
|
|
534
|
+
end
|
|
535
|
+
|
|
536
|
+
context 'search on an `in` predicate with an array' do
|
|
537
|
+
it 'should function correctly when passing an array of ids' do
|
|
538
|
+
array = Person.all.map(&:id)
|
|
539
|
+
s = Person.ransack(id_in: array)
|
|
540
|
+
expect(s.result.count).to eq array.size
|
|
541
|
+
end
|
|
542
|
+
end
|
|
543
|
+
|
|
544
|
+
it 'should work correctly when an attribute name ends with _start' do
|
|
545
|
+
p = Person.create!(new_start: 'Bar and foo', name: 'Xiang')
|
|
546
|
+
|
|
547
|
+
s = Person.ransack(new_start_end: ' and foo')
|
|
548
|
+
expect(s.result.to_a).to eq [p]
|
|
549
|
+
|
|
550
|
+
s = Person.ransack(name_or_new_start_start: 'Xia')
|
|
551
|
+
expect(s.result.to_a).to eq [p]
|
|
552
|
+
|
|
553
|
+
s = Person.ransack(new_start_or_name_end: 'iang')
|
|
554
|
+
expect(s.result.to_a).to eq [p]
|
|
555
|
+
end
|
|
556
|
+
|
|
557
|
+
it 'should work correctly when an attribute name ends with _end' do
|
|
558
|
+
p = Person.create!(stop_end: 'Foo and bar', name: 'Marianne')
|
|
559
|
+
|
|
560
|
+
s = Person.ransack(stop_end_start: 'Foo and')
|
|
561
|
+
expect(s.result.to_a).to eq [p]
|
|
562
|
+
|
|
563
|
+
s = Person.ransack(stop_end_or_name_end: 'anne')
|
|
564
|
+
expect(s.result.to_a).to eq [p]
|
|
565
|
+
|
|
566
|
+
s = Person.ransack(name_or_stop_end_end: ' bar')
|
|
567
|
+
expect(s.result.to_a).to eq [p]
|
|
568
|
+
end
|
|
569
|
+
|
|
570
|
+
it 'should work correctly when an attribute name has `and` in it' do
|
|
571
|
+
p = Person.create!(terms_and_conditions: true)
|
|
572
|
+
s = Person.ransack(terms_and_conditions_eq: true)
|
|
573
|
+
expect(s.result.to_a).to eq [p]
|
|
574
|
+
end
|
|
575
|
+
|
|
576
|
+
context 'attribute aliased column names',
|
|
577
|
+
if: Ransack::SUPPORTS_ATTRIBUTE_ALIAS do
|
|
578
|
+
it 'should be translated to original column name' do
|
|
579
|
+
s = Person.ransack(full_name_eq: 'Nicolas Cage')
|
|
580
|
+
expect(s.result.to_sql).to match(
|
|
581
|
+
/WHERE #{quote_table_name("people")}.#{quote_column_name("name")}/
|
|
582
|
+
)
|
|
583
|
+
end
|
|
584
|
+
|
|
585
|
+
it 'should translate on associations' do
|
|
586
|
+
s = Person.ransack(articles_content_cont: 'Nicolas Cage')
|
|
587
|
+
expect(s.result.to_sql).to match(
|
|
588
|
+
/#{quote_table_name("articles")}.#{
|
|
589
|
+
quote_column_name("body")} I?LIKE '%Nicolas Cage%'/
|
|
590
|
+
)
|
|
591
|
+
end
|
|
592
|
+
end
|
|
593
|
+
|
|
594
|
+
it 'sorts with different join variants' do
|
|
595
|
+
comments = [
|
|
596
|
+
Comment.create(article: Article.create(title: 'Avenger'), person: Person.create(salary: 100_000)),
|
|
597
|
+
Comment.create(article: Article.create(title: 'Avenge'), person: Person.create(salary: 50_000)),
|
|
598
|
+
]
|
|
599
|
+
expect(Comment.ransack(article_title_cont: 'aven', s: 'person_salary desc').result).to eq(comments)
|
|
600
|
+
expect(Comment.joins(:person).ransack(s: 'persons_salarydesc', article_title_cont: 'aven').result).to eq(comments)
|
|
601
|
+
expect(Comment.joins(:person).ransack(article_title_cont: 'aven', s: 'persons_salary desc').result).to eq(comments)
|
|
602
|
+
end
|
|
603
|
+
|
|
604
|
+
it 'allows sort by `only_sort` field' do
|
|
605
|
+
s = Person.ransack(
|
|
606
|
+
's' => { '0' => { 'dir' => 'asc', 'name' => 'only_sort' } }
|
|
607
|
+
)
|
|
608
|
+
expect(s.result.to_sql).to match(
|
|
609
|
+
/ORDER BY #{quote_table_name("people")}.#{
|
|
610
|
+
quote_column_name("only_sort")} ASC/
|
|
611
|
+
)
|
|
612
|
+
end
|
|
613
|
+
|
|
614
|
+
it 'does not sort by `only_search` field' do
|
|
615
|
+
s = Person.ransack(
|
|
616
|
+
's' => { '0' => { 'dir' => 'asc', 'name' => 'only_search' } }
|
|
617
|
+
)
|
|
618
|
+
expect(s.result.to_sql).not_to match(
|
|
619
|
+
/ORDER BY #{quote_table_name("people")}.#{
|
|
620
|
+
quote_column_name("only_search")} ASC/
|
|
621
|
+
)
|
|
622
|
+
end
|
|
623
|
+
|
|
624
|
+
it 'allows search by `only_search` field' do
|
|
625
|
+
s = Person.ransack(only_search_eq: 'htimS cirA')
|
|
626
|
+
expect(s.result.to_sql).to match(
|
|
627
|
+
/WHERE #{quote_table_name("people")}.#{
|
|
628
|
+
quote_column_name("only_search")} = 'htimS cirA'/
|
|
629
|
+
)
|
|
630
|
+
end
|
|
631
|
+
|
|
632
|
+
it 'cannot be searched by `only_sort`' do
|
|
633
|
+
s = Person.ransack(only_sort_eq: 'htimS cirA')
|
|
634
|
+
expect(s.result.to_sql).not_to match(
|
|
635
|
+
/WHERE #{quote_table_name("people")}.#{
|
|
636
|
+
quote_column_name("only_sort")} = 'htimS cirA'/
|
|
637
|
+
)
|
|
638
|
+
end
|
|
639
|
+
|
|
640
|
+
it 'allows sort by `only_admin` field, if auth_object: :admin' do
|
|
641
|
+
s = Person.ransack(
|
|
642
|
+
{ 's' => { '0' => { 'dir' => 'asc', 'name' => 'only_admin' } } },
|
|
643
|
+
{ auth_object: :admin }
|
|
644
|
+
)
|
|
645
|
+
expect(s.result.to_sql).to match(
|
|
646
|
+
/ORDER BY #{quote_table_name("people")}.#{
|
|
647
|
+
quote_column_name("only_admin")} ASC/
|
|
648
|
+
)
|
|
649
|
+
end
|
|
650
|
+
|
|
651
|
+
it 'does not sort by `only_admin` field, if auth_object: nil' do
|
|
652
|
+
s = Person.ransack(
|
|
653
|
+
's' => { '0' => { 'dir' => 'asc', 'name' => 'only_admin' } }
|
|
654
|
+
)
|
|
655
|
+
expect(s.result.to_sql).not_to match(
|
|
656
|
+
/ORDER BY #{quote_table_name("people")}.#{
|
|
657
|
+
quote_column_name("only_admin")} ASC/
|
|
658
|
+
)
|
|
659
|
+
end
|
|
660
|
+
|
|
661
|
+
it 'allows search by `only_admin` field, if auth_object: :admin' do
|
|
662
|
+
s = Person.ransack(
|
|
663
|
+
{ only_admin_eq: 'htimS cirA' },
|
|
664
|
+
{ auth_object: :admin }
|
|
665
|
+
)
|
|
666
|
+
expect(s.result.to_sql).to match(
|
|
667
|
+
/WHERE #{quote_table_name("people")}.#{
|
|
668
|
+
quote_column_name("only_admin")} = 'htimS cirA'/
|
|
669
|
+
)
|
|
670
|
+
end
|
|
671
|
+
|
|
672
|
+
it 'cannot be searched by `only_admin`, if auth_object: nil' do
|
|
673
|
+
s = Person.ransack(only_admin_eq: 'htimS cirA')
|
|
674
|
+
expect(s.result.to_sql).not_to match(
|
|
675
|
+
/WHERE #{quote_table_name("people")}.#{
|
|
676
|
+
quote_column_name("only_admin")} = 'htimS cirA'/
|
|
677
|
+
)
|
|
678
|
+
end
|
|
679
|
+
|
|
680
|
+
it 'should allow passing ransacker arguments to a ransacker' do
|
|
681
|
+
s = Person.ransack(
|
|
682
|
+
c: [{
|
|
683
|
+
a: {
|
|
684
|
+
'0' => {
|
|
685
|
+
name: 'with_arguments', ransacker_args: [10, 100]
|
|
686
|
+
}
|
|
687
|
+
},
|
|
688
|
+
p: 'cont',
|
|
689
|
+
v: ['Passing arguments to ransackers!']
|
|
690
|
+
}]
|
|
691
|
+
)
|
|
692
|
+
expect(s.result.to_sql).to match(
|
|
693
|
+
/LENGTH\(articles.body\) BETWEEN 10 AND 100/
|
|
694
|
+
)
|
|
695
|
+
expect(s.result.to_sql).to match(
|
|
696
|
+
/LIKE \'\%Passing arguments to ransackers!\%\'/
|
|
697
|
+
)
|
|
698
|
+
expect { s.result.first }.to_not raise_error
|
|
699
|
+
end
|
|
700
|
+
|
|
701
|
+
it 'should allow sort passing arguments to a ransacker' do
|
|
702
|
+
s = Person.ransack(
|
|
703
|
+
s: {
|
|
704
|
+
'0' => {
|
|
705
|
+
name: 'with_arguments', dir: 'desc', ransacker_args: [2, 6]
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
)
|
|
709
|
+
expect(s.result.to_sql).to match(
|
|
710
|
+
/ORDER BY \(SELECT MAX\(articles.title\) FROM articles/
|
|
711
|
+
)
|
|
712
|
+
expect(s.result.to_sql).to match(
|
|
713
|
+
/WHERE articles.person_id = people.id AND LENGTH\(articles.body\)/
|
|
714
|
+
)
|
|
715
|
+
expect(s.result.to_sql).to match(
|
|
716
|
+
/BETWEEN 2 AND 6 GROUP BY articles.person_id \) DESC/
|
|
717
|
+
)
|
|
718
|
+
end
|
|
719
|
+
|
|
720
|
+
context 'case insensitive sorting' do
|
|
721
|
+
it 'allows sort by desc' do
|
|
722
|
+
search = Person.ransack(sorts: ['name_case_insensitive desc'])
|
|
723
|
+
expect(search.result.to_sql).to match /ORDER BY LOWER(.*) DESC/
|
|
724
|
+
end
|
|
725
|
+
|
|
726
|
+
it 'allows sort by asc' do
|
|
727
|
+
search = Person.ransack(sorts: ['name_case_insensitive asc'])
|
|
728
|
+
expect(search.result.to_sql).to match /ORDER BY LOWER(.*) ASC/
|
|
729
|
+
end
|
|
730
|
+
end
|
|
731
|
+
|
|
732
|
+
context 'ransacker with different types' do
|
|
733
|
+
it 'handles string type ransacker correctly' do
|
|
734
|
+
s = Person.ransack(name_case_insensitive_eq: 'test')
|
|
735
|
+
expect(s.result.to_sql).to match(/LOWER\(.*\) = 'test'/)
|
|
736
|
+
end
|
|
737
|
+
|
|
738
|
+
it 'handles integer type ransacker correctly' do
|
|
739
|
+
s = Person.ransack(sql_literal_id_eq: 1)
|
|
740
|
+
expect(s.result.to_sql).to match(/people\.id = 1/)
|
|
741
|
+
end
|
|
742
|
+
end
|
|
743
|
+
|
|
744
|
+
context 'ransacker with formatter returning nil' do
|
|
745
|
+
it 'handles formatter returning nil gracefully' do
|
|
746
|
+
# This tests the edge case where a formatter might return nil
|
|
747
|
+
s = Person.ransack(article_tags_eq: 999999) # Non-existent tag ID
|
|
748
|
+
expect { s.result.to_sql }.not_to raise_error
|
|
749
|
+
end
|
|
750
|
+
end
|
|
751
|
+
|
|
752
|
+
context 'ransacker with array formatters' do
|
|
753
|
+
it 'handles array_people_ids formatter correctly' do
|
|
754
|
+
person1 = Person.create!(name: 'Test1')
|
|
755
|
+
person2 = Person.create!(name: 'Test2')
|
|
756
|
+
|
|
757
|
+
s = Person.ransack(array_people_ids_eq: 'test')
|
|
758
|
+
expect { s.result }.not_to raise_error
|
|
759
|
+
end
|
|
760
|
+
|
|
761
|
+
it 'handles array_where_people_ids formatter correctly' do
|
|
762
|
+
person1 = Person.create!(name: 'Test1')
|
|
763
|
+
person2 = Person.create!(name: 'Test2')
|
|
764
|
+
|
|
765
|
+
s = Person.ransack(array_where_people_ids_eq: [person1.id, person2.id])
|
|
766
|
+
expect { s.result }.not_to raise_error
|
|
767
|
+
end
|
|
768
|
+
end
|
|
769
|
+
|
|
770
|
+
context 'regular sorting' do
|
|
771
|
+
it 'allows sort by desc' do
|
|
772
|
+
search = Person.ransack(sorts: ['name desc'])
|
|
773
|
+
expect(search.result.to_sql).to match /ORDER BY .* DESC/
|
|
774
|
+
end
|
|
775
|
+
|
|
776
|
+
it 'allows sort by asc' do
|
|
777
|
+
search = Person.ransack(sorts: ['name asc'])
|
|
778
|
+
expect(search.result.to_sql).to match /ORDER BY .* ASC/
|
|
779
|
+
end
|
|
780
|
+
end
|
|
781
|
+
|
|
782
|
+
context 'sorting by a scope' do
|
|
783
|
+
it 'applies the correct scope' do
|
|
784
|
+
search = Person.ransack(sorts: ['reverse_name asc'])
|
|
785
|
+
expect(search.result.to_sql).to include("ORDER BY REVERSE(name) ASC")
|
|
786
|
+
end
|
|
787
|
+
end
|
|
788
|
+
end
|
|
789
|
+
|
|
790
|
+
describe '#ransackable_attributes' do
|
|
791
|
+
context 'when auth_object is nil' do
|
|
792
|
+
subject { Person.ransackable_attributes }
|
|
793
|
+
|
|
794
|
+
it { should include 'name' }
|
|
795
|
+
it { should include 'reversed_name' }
|
|
796
|
+
it { should include 'doubled_name' }
|
|
797
|
+
it { should include 'term' }
|
|
798
|
+
it { should include 'only_search' }
|
|
799
|
+
it { should_not include 'only_sort' }
|
|
800
|
+
it { should_not include 'only_admin' }
|
|
801
|
+
|
|
802
|
+
if Ransack::SUPPORTS_ATTRIBUTE_ALIAS
|
|
803
|
+
it { should include 'full_name' }
|
|
804
|
+
end
|
|
805
|
+
end
|
|
806
|
+
|
|
807
|
+
context 'with auth_object :admin' do
|
|
808
|
+
subject { Person.ransackable_attributes(:admin) }
|
|
809
|
+
|
|
810
|
+
it { should include 'name' }
|
|
811
|
+
it { should include 'reversed_name' }
|
|
812
|
+
it { should include 'doubled_name' }
|
|
813
|
+
it { should include 'only_search' }
|
|
814
|
+
it { should_not include 'only_sort' }
|
|
815
|
+
it { should include 'only_admin' }
|
|
816
|
+
end
|
|
817
|
+
|
|
818
|
+
context 'when not defined in model, nor in ApplicationRecord' do
|
|
819
|
+
subject { Article.ransackable_attributes }
|
|
820
|
+
|
|
821
|
+
it "raises a helpful error" do
|
|
822
|
+
without_application_record_method(:ransackable_attributes) do
|
|
823
|
+
expect { subject }.to raise_error(RuntimeError, /Ransack needs Article attributes explicitly allowlisted/)
|
|
824
|
+
end
|
|
825
|
+
end
|
|
826
|
+
end
|
|
827
|
+
|
|
828
|
+
context 'when defined only in model by delegating to super' do
|
|
829
|
+
subject { Article.ransackable_attributes }
|
|
830
|
+
|
|
831
|
+
around do |example|
|
|
832
|
+
Article.singleton_class.define_method(:ransackable_attributes) do
|
|
833
|
+
super(nil) - super(nil)
|
|
834
|
+
end
|
|
835
|
+
|
|
836
|
+
example.run
|
|
837
|
+
ensure
|
|
838
|
+
Article.singleton_class.remove_method(:ransackable_attributes)
|
|
839
|
+
end
|
|
840
|
+
|
|
841
|
+
it "returns the allowlist in the model, and warns" do
|
|
842
|
+
without_application_record_method(:ransackable_attributes) do
|
|
843
|
+
expect { subject }.to output(/Ransack's builtin `ransackable_attributes` method is deprecated/).to_stderr
|
|
844
|
+
expect(subject).to be_empty
|
|
845
|
+
end
|
|
846
|
+
end
|
|
847
|
+
end
|
|
848
|
+
end
|
|
849
|
+
|
|
850
|
+
describe '#ransortable_attributes' do
|
|
851
|
+
context 'when auth_object is nil' do
|
|
852
|
+
subject { Person.ransortable_attributes }
|
|
853
|
+
|
|
854
|
+
it { should include 'name' }
|
|
855
|
+
it { should include 'reversed_name' }
|
|
856
|
+
it { should include 'doubled_name' }
|
|
857
|
+
it { should include 'only_sort' }
|
|
858
|
+
it { should_not include 'only_search' }
|
|
859
|
+
it { should_not include 'only_admin' }
|
|
860
|
+
end
|
|
861
|
+
|
|
862
|
+
context 'with auth_object :admin' do
|
|
863
|
+
subject { Person.ransortable_attributes(:admin) }
|
|
864
|
+
|
|
865
|
+
it { should include 'name' }
|
|
866
|
+
it { should include 'reversed_name' }
|
|
867
|
+
it { should include 'doubled_name' }
|
|
868
|
+
it { should include 'only_sort' }
|
|
869
|
+
it { should_not include 'only_search' }
|
|
870
|
+
it { should include 'only_admin' }
|
|
871
|
+
end
|
|
872
|
+
end
|
|
873
|
+
|
|
874
|
+
describe '#ransackable_associations' do
|
|
875
|
+
subject { Person.ransackable_associations }
|
|
876
|
+
|
|
877
|
+
it { should include 'parent' }
|
|
878
|
+
it { should include 'children' }
|
|
879
|
+
it { should include 'articles' }
|
|
880
|
+
|
|
881
|
+
context 'when not defined in model, nor in ApplicationRecord' do
|
|
882
|
+
subject { Article.ransackable_associations }
|
|
883
|
+
|
|
884
|
+
it "raises a helpful error" do
|
|
885
|
+
without_application_record_method(:ransackable_associations) do
|
|
886
|
+
expect { subject }.to raise_error(RuntimeError, /Ransack needs Article associations explicitly allowlisted/)
|
|
887
|
+
end
|
|
888
|
+
end
|
|
889
|
+
end
|
|
890
|
+
|
|
891
|
+
context 'when defined only in model by delegating to super' do
|
|
892
|
+
subject { Article.ransackable_associations }
|
|
893
|
+
|
|
894
|
+
around do |example|
|
|
895
|
+
Article.singleton_class.define_method(:ransackable_associations) do
|
|
896
|
+
super(nil) - super(nil)
|
|
897
|
+
end
|
|
898
|
+
|
|
899
|
+
example.run
|
|
900
|
+
ensure
|
|
901
|
+
Article.singleton_class.remove_method(:ransackable_associations)
|
|
902
|
+
end
|
|
903
|
+
|
|
904
|
+
it "returns the allowlist in the model, and warns" do
|
|
905
|
+
without_application_record_method(:ransackable_associations) do
|
|
906
|
+
expect { subject }.to output(/Ransack's builtin `ransackable_associations` method is deprecated/).to_stderr
|
|
907
|
+
expect(subject).to be_empty
|
|
908
|
+
end
|
|
909
|
+
end
|
|
910
|
+
end
|
|
911
|
+
end
|
|
912
|
+
|
|
913
|
+
describe '#ransackable_scopes' do
|
|
914
|
+
subject { Person.ransackable_scopes }
|
|
915
|
+
|
|
916
|
+
it { should eq [] }
|
|
917
|
+
end
|
|
918
|
+
|
|
919
|
+
describe '#ransackable_scopes_skip_sanitize_args' do
|
|
920
|
+
subject { Person.ransackable_scopes_skip_sanitize_args }
|
|
921
|
+
|
|
922
|
+
it { should eq [] }
|
|
923
|
+
end
|
|
924
|
+
|
|
925
|
+
private
|
|
926
|
+
|
|
927
|
+
def without_application_record_method(method)
|
|
928
|
+
ApplicationRecord.singleton_class.alias_method :"original_#{method}", :"#{method}"
|
|
929
|
+
ApplicationRecord.singleton_class.remove_method :"#{method}"
|
|
930
|
+
|
|
931
|
+
yield
|
|
932
|
+
ensure
|
|
933
|
+
ApplicationRecord.singleton_class.alias_method :"#{method}", :"original_#{method}"
|
|
934
|
+
ApplicationRecord.singleton_class.remove_method :"original_#{method}"
|
|
935
|
+
end
|
|
936
|
+
|
|
937
|
+
def rails7_and_mysql
|
|
938
|
+
::ActiveRecord::VERSION::MAJOR >= 7 && ENV['DB'] == 'mysql'
|
|
939
|
+
end
|
|
940
|
+
end
|
|
941
|
+
end
|
|
942
|
+
end
|
|
943
|
+
end
|