ransack 1.4.1 → 1.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +75 -0
- data/CONTRIBUTING.md +7 -7
- data/Gemfile +7 -0
- data/README.md +139 -34
- data/lib/ransack/adapters/active_record/3.0/compat.rb +5 -5
- data/lib/ransack/adapters/active_record/3.0/context.rb +32 -16
- data/lib/ransack/adapters/active_record/3.1/context.rb +31 -16
- data/lib/ransack/adapters/active_record/context.rb +34 -26
- data/lib/ransack/configuration.rb +1 -1
- data/lib/ransack/constants.rb +72 -37
- data/lib/ransack/context.rb +18 -12
- data/lib/ransack/helpers/form_builder.rb +32 -15
- data/lib/ransack/helpers/form_helper.rb +83 -55
- data/lib/ransack/naming.rb +1 -1
- data/lib/ransack/nodes/condition.rb +9 -9
- data/lib/ransack/nodes/grouping.rb +15 -9
- data/lib/ransack/nodes/sort.rb +9 -4
- data/lib/ransack/predicate.rb +10 -10
- data/lib/ransack/search.rb +15 -8
- data/lib/ransack/translate.rb +9 -6
- data/lib/ransack/version.rb +1 -1
- data/lib/ransack/visitor.rb +8 -2
- data/spec/ransack/adapters/active_record/base_spec.rb +2 -2
- data/spec/ransack/helpers/form_builder_spec.rb +63 -43
- data/spec/ransack/helpers/form_helper_spec.rb +163 -2
- data/spec/ransack/nodes/grouping_spec.rb +44 -1
- data/spec/ransack/predicate_spec.rb +165 -3
- data/spec/ransack/translate_spec.rb +4 -1
- data/spec/support/en.yml +5 -0
- data/spec/support/schema.rb +6 -0
- metadata +3 -3
@@ -51,8 +51,8 @@ module Ransack
|
|
51
51
|
end
|
52
52
|
|
53
53
|
describe '#sort_link with default search_key defined as symbol' do
|
54
|
-
subject { @controller.
|
55
|
-
|
54
|
+
subject { @controller.view_context
|
55
|
+
.sort_link(
|
56
56
|
Person.search(
|
57
57
|
{ :sorts => ['name desc'] }, :search_key => :people_search
|
58
58
|
),
|
@@ -71,6 +71,46 @@ module Ransack
|
|
71
71
|
}
|
72
72
|
end
|
73
73
|
|
74
|
+
describe '#sort_link desc through association table defined as a symbol' do
|
75
|
+
subject { @controller.view_context
|
76
|
+
.sort_link(
|
77
|
+
Person.search({ :sorts => 'comments_body asc' }),
|
78
|
+
:comments_body, :controller => 'people'
|
79
|
+
)
|
80
|
+
}
|
81
|
+
it {
|
82
|
+
should match(
|
83
|
+
if ActiveRecord::VERSION::STRING =~ /^3\.[1-2]\./
|
84
|
+
/people\?q%5Bs%5D=comments.body\+desc/
|
85
|
+
else
|
86
|
+
/people\?q(%5B|\[)s(%5D|\])=comments.body\+desc/
|
87
|
+
end
|
88
|
+
)
|
89
|
+
}
|
90
|
+
it { should match /sort_link asc/ }
|
91
|
+
it { should match /Body ▲/ }
|
92
|
+
end
|
93
|
+
|
94
|
+
describe '#sort_link through association table defined as a string' do
|
95
|
+
subject { @controller.view_context
|
96
|
+
.sort_link(
|
97
|
+
Person.search({ :sorts => 'comments.body desc' }),
|
98
|
+
'comments.body', :controller => 'people'
|
99
|
+
)
|
100
|
+
}
|
101
|
+
it {
|
102
|
+
should match(
|
103
|
+
if ActiveRecord::VERSION::STRING =~ /^3\.[1-2]\./
|
104
|
+
/people\?q%5Bs%5D=comments.body\+asc/
|
105
|
+
else
|
106
|
+
/people\?q(%5B|\[)s(%5D|\])=comments.body\+asc/
|
107
|
+
end
|
108
|
+
)
|
109
|
+
}
|
110
|
+
it { should match /sort_link desc/ }
|
111
|
+
it { should match /Comments.body ▼/ }
|
112
|
+
end
|
113
|
+
|
74
114
|
describe '#sort_link works even if search params are a blank string' do
|
75
115
|
before { @controller.view_context.params[:q] = '' }
|
76
116
|
specify {
|
@@ -105,6 +145,127 @@ module Ransack
|
|
105
145
|
}
|
106
146
|
end
|
107
147
|
|
148
|
+
describe '#sort_link with multiple search_keys defined as an array' do
|
149
|
+
subject { @controller.view_context
|
150
|
+
.sort_link(
|
151
|
+
[:main_app, Person.search(:sorts => ['name desc', 'email asc'])],
|
152
|
+
:name, [:name, 'email DESC'],
|
153
|
+
:controller => 'people'
|
154
|
+
)
|
155
|
+
}
|
156
|
+
it {
|
157
|
+
should match(
|
158
|
+
/people\?q(%5B|\[)s(%5D|\])(%5B|\[)(%5D|\])=name\+asc&q(%5B|\[)s(%5D|\])(%5B|\[)(%5D|\])=email\+desc/
|
159
|
+
)
|
160
|
+
}
|
161
|
+
it {
|
162
|
+
should match /sort_link desc/
|
163
|
+
}
|
164
|
+
it {
|
165
|
+
should match /Full Name ▼/
|
166
|
+
}
|
167
|
+
end
|
168
|
+
|
169
|
+
describe '#sort_link with multiple search_keys should allow a label to be specified' do
|
170
|
+
subject { @controller.view_context
|
171
|
+
.sort_link(
|
172
|
+
[:main_app, Person.search(:sorts => ['name desc', 'email asc'])],
|
173
|
+
:name, [:name, 'email DESC'],
|
174
|
+
'Property Name',
|
175
|
+
:controller => 'people'
|
176
|
+
)
|
177
|
+
}
|
178
|
+
it {
|
179
|
+
should match /Property Name ▼/
|
180
|
+
}
|
181
|
+
end
|
182
|
+
|
183
|
+
describe '#sort_link with multiple search_keys should flip multiple fields specified without a direction' do
|
184
|
+
subject { @controller.view_context
|
185
|
+
.sort_link(
|
186
|
+
[:main_app, Person.search(:sorts => ['name desc', 'email asc'])],
|
187
|
+
:name, [:name, :email],
|
188
|
+
:controller => 'people'
|
189
|
+
)
|
190
|
+
}
|
191
|
+
it {
|
192
|
+
should match(
|
193
|
+
/people\?q(%5B|\[)s(%5D|\])(%5B|\[)(%5D|\])=name\+asc&q(%5B|\[)s(%5D|\])(%5B|\[)(%5D|\])=email\+desc/
|
194
|
+
)
|
195
|
+
}
|
196
|
+
it {
|
197
|
+
should match /sort_link desc/
|
198
|
+
}
|
199
|
+
it {
|
200
|
+
should match /Full Name ▼/
|
201
|
+
}
|
202
|
+
end
|
203
|
+
|
204
|
+
describe '#sort_link with multiple search_keys should allow a default_order to be specified' do
|
205
|
+
subject { @controller.view_context
|
206
|
+
.sort_link(
|
207
|
+
[:main_app, Person.search()],
|
208
|
+
:name, [:name, :email],
|
209
|
+
:controller => 'people',
|
210
|
+
:default_order => 'desc'
|
211
|
+
)
|
212
|
+
}
|
213
|
+
it {
|
214
|
+
should match(
|
215
|
+
/people\?q(%5B|\[)s(%5D|\])(%5B|\[)(%5D|\])=name\+desc&q(%5B|\[)s(%5D|\])(%5B|\[)(%5D|\])=email\+desc/
|
216
|
+
)
|
217
|
+
}
|
218
|
+
it {
|
219
|
+
should match /sort_link/
|
220
|
+
}
|
221
|
+
it {
|
222
|
+
should match /Full Name/
|
223
|
+
}
|
224
|
+
end
|
225
|
+
|
226
|
+
describe '#sort_link with multiple search_keys should allow multiple default_orders to be specified' do
|
227
|
+
subject { @controller.view_context
|
228
|
+
.sort_link(
|
229
|
+
[:main_app, Person.search()],
|
230
|
+
:name, [:name, :email],
|
231
|
+
:controller => 'people',
|
232
|
+
:default_order => { 'name' => 'desc', :email => 'asc' }
|
233
|
+
)
|
234
|
+
}
|
235
|
+
it {
|
236
|
+
should match(
|
237
|
+
/people\?q(%5B|\[)s(%5D|\])(%5B|\[)(%5D|\])=name\+desc&q(%5B|\[)s(%5D|\])(%5B|\[)(%5D|\])=email\+asc/
|
238
|
+
)
|
239
|
+
}
|
240
|
+
it {
|
241
|
+
should match /sort_link/
|
242
|
+
}
|
243
|
+
it {
|
244
|
+
should match /Full Name/
|
245
|
+
}
|
246
|
+
end
|
247
|
+
|
248
|
+
describe '#sort_link with multiple search_keys with multiple default_orders should not override a specified order' do
|
249
|
+
subject { @controller.view_context
|
250
|
+
.sort_link(
|
251
|
+
[:main_app, Person.search()],
|
252
|
+
:name, [:name, 'email desc'],
|
253
|
+
:controller => 'people',
|
254
|
+
:default_order => { 'name' => 'desc', :email => 'asc' }
|
255
|
+
)
|
256
|
+
}
|
257
|
+
it {
|
258
|
+
should match(
|
259
|
+
/people\?q(%5B|\[)s(%5D|\])(%5B|\[)(%5D|\])=name\+desc&q(%5B|\[)s(%5D|\])(%5B|\[)(%5D|\])=email\+desc/
|
260
|
+
)
|
261
|
+
}
|
262
|
+
it {
|
263
|
+
should match /sort_link/
|
264
|
+
}
|
265
|
+
it {
|
266
|
+
should match /Full Name/
|
267
|
+
}
|
268
|
+
end
|
108
269
|
context 'view has existing parameters' do
|
109
270
|
before do
|
110
271
|
@controller.view_context.params.merge!({ :exist => 'existing' })
|
@@ -2,12 +2,55 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
module Ransack
|
4
4
|
module Nodes
|
5
|
+
|
5
6
|
describe Grouping do
|
7
|
+
|
6
8
|
before do
|
7
9
|
@g = 1
|
8
10
|
end
|
9
11
|
|
12
|
+
let(:context) { Context.for(Person) }
|
13
|
+
|
14
|
+
subject { described_class.new(context) }
|
15
|
+
|
16
|
+
describe '#attribute_method?' do
|
17
|
+
context 'for attributes of the context' do
|
18
|
+
it 'is true' do
|
19
|
+
expect(subject.attribute_method?('name')).to be_true
|
20
|
+
end
|
21
|
+
|
22
|
+
context "when the attribute contains '_and_'" do
|
23
|
+
it 'is true' do
|
24
|
+
expect(subject.attribute_method?('terms_and_conditions')).to be_true
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
context "when the attribute contains '_or_'" do
|
29
|
+
it 'is true' do
|
30
|
+
expect(subject.attribute_method?('true_or_false')).to be_true
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
context "when the attribute ends with '_start'" do
|
35
|
+
it 'is true' do
|
36
|
+
expect(subject.attribute_method?('life_start')).to be_true
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
context "when the attribute ends with '_end'" do
|
41
|
+
it 'is true' do
|
42
|
+
expect(subject.attribute_method?('stop_end')).to be_true
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
context 'for unknown attributes' do
|
48
|
+
it 'is false' do
|
49
|
+
expect(subject.attribute_method?('not_an_attribute')).to be_false
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
10
53
|
|
11
54
|
end
|
12
55
|
end
|
13
|
-
end
|
56
|
+
end
|
@@ -22,8 +22,7 @@ module Ransack
|
|
22
22
|
describe 'eq' do
|
23
23
|
it 'generates an equality condition for boolean true' do
|
24
24
|
@s.awesome_eq = true
|
25
|
-
field = "#{quote_table_name("people")}.#{
|
26
|
-
quote_column_name("awesome")}"
|
25
|
+
field = "#{quote_table_name("people")}.#{quote_column_name("awesome")}"
|
27
26
|
expect(@s.result.to_sql).to match /#{field} = #{
|
28
27
|
ActiveRecord::Base.connection.quoted_true}/
|
29
28
|
end
|
@@ -41,8 +40,91 @@ module Ransack
|
|
41
40
|
end
|
42
41
|
end
|
43
42
|
|
44
|
-
describe '
|
43
|
+
describe 'lteq' do
|
44
|
+
it 'generates a <= condition with an integer column' do
|
45
|
+
val = 1000
|
46
|
+
@s.salary_lteq = val
|
47
|
+
field = "#{quote_table_name("people")}.#{quote_column_name("salary")}"
|
48
|
+
expect(@s.result.to_sql).to match /#{field} <= #{val}/
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'generates a <= condition with a string column' do
|
52
|
+
val = 'jane@doe.com'
|
53
|
+
@s.email_lteq = val
|
54
|
+
field = "#{quote_table_name("people")}.#{quote_column_name("email")}"
|
55
|
+
expect(@s.result.to_sql).to match /#{field} <= '#{val}'/
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'does not generate a condition for nil' do
|
59
|
+
@s.salary_lteq = nil
|
60
|
+
expect(@s.result.to_sql).not_to match /WHERE/
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
describe 'lt' do
|
65
|
+
it 'generates a < condition with an integer column' do
|
66
|
+
val = 2000
|
67
|
+
@s.salary_lt = val
|
68
|
+
field = "#{quote_table_name("people")}.#{quote_column_name("salary")}"
|
69
|
+
expect(@s.result.to_sql).to match /#{field} < #{val}/
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'generates a < condition with a string column' do
|
73
|
+
val = 'jane@doe.com'
|
74
|
+
@s.email_lt = val
|
75
|
+
field = "#{quote_table_name("people")}.#{quote_column_name("email")}"
|
76
|
+
expect(@s.result.to_sql).to match /#{field} < '#{val}'/
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'does not generate a condition for nil' do
|
80
|
+
@s.salary_lt = nil
|
81
|
+
expect(@s.result.to_sql).not_to match /WHERE/
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
describe 'gteq' do
|
86
|
+
it 'generates a >= condition with an integer column' do
|
87
|
+
val = 300
|
88
|
+
@s.salary_gteq = val
|
89
|
+
field = "#{quote_table_name("people")}.#{quote_column_name("salary")}"
|
90
|
+
expect(@s.result.to_sql).to match /#{field} >= #{val}/
|
91
|
+
end
|
92
|
+
|
93
|
+
it 'generates a >= condition with a string column' do
|
94
|
+
val = 'jane@doe.com'
|
95
|
+
@s.email_gteq = val
|
96
|
+
field = "#{quote_table_name("people")}.#{quote_column_name("email")}"
|
97
|
+
expect(@s.result.to_sql).to match /#{field} >= '#{val}'/
|
98
|
+
end
|
99
|
+
|
100
|
+
it 'does not generate a condition for nil' do
|
101
|
+
@s.salary_gteq = nil
|
102
|
+
expect(@s.result.to_sql).not_to match /WHERE/
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
describe 'gt' do
|
107
|
+
it 'generates a > condition with an integer column' do
|
108
|
+
val = 400
|
109
|
+
@s.salary_gt = val
|
110
|
+
field = "#{quote_table_name("people")}.#{quote_column_name("salary")}"
|
111
|
+
expect(@s.result.to_sql).to match /#{field} > #{val}/
|
112
|
+
end
|
113
|
+
|
114
|
+
it 'generates a > condition with a string column' do
|
115
|
+
val = 'jane@doe.com'
|
116
|
+
@s.email_gt = val
|
117
|
+
field = "#{quote_table_name("people")}.#{quote_column_name("email")}"
|
118
|
+
expect(@s.result.to_sql).to match /#{field} > '#{val}'/
|
119
|
+
end
|
120
|
+
|
121
|
+
it 'does not generate a condition for nil' do
|
122
|
+
@s.salary_gt = nil
|
123
|
+
expect(@s.result.to_sql).not_to match /WHERE/
|
124
|
+
end
|
125
|
+
end
|
45
126
|
|
127
|
+
describe 'cont' do
|
46
128
|
it_has_behavior 'wildcard escaping', :name_cont,
|
47
129
|
(if ActiveRecord::Base.connection.adapter_name == "PostgreSQL"
|
48
130
|
/"people"."name" ILIKE '%\\%\\._\\\\%'/
|
@@ -80,6 +162,86 @@ module Ransack
|
|
80
162
|
end
|
81
163
|
end
|
82
164
|
|
165
|
+
describe 'start' do
|
166
|
+
it 'generates a LIKE query with value followed by %' do
|
167
|
+
@s.name_start = 'Er'
|
168
|
+
field = "#{quote_table_name("people")}.#{quote_column_name("name")}"
|
169
|
+
expect(@s.result.to_sql).to match /#{field} I?LIKE 'Er%'/
|
170
|
+
end
|
171
|
+
|
172
|
+
it "works with attribute names ending with '_start'" do
|
173
|
+
@s.new_start_start = 'hEy'
|
174
|
+
field = "#{quote_table_name("people")}.#{quote_column_name("new_start")}"
|
175
|
+
expect(@s.result.to_sql).to match /#{field} I?LIKE 'hEy%'/
|
176
|
+
end
|
177
|
+
|
178
|
+
it "works with attribute names ending with '_end'" do
|
179
|
+
@s.stop_end_start = 'begin'
|
180
|
+
field = "#{quote_table_name("people")}.#{quote_column_name("stop_end")}"
|
181
|
+
expect(@s.result.to_sql).to match /#{field} I?LIKE 'begin%'/
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
describe 'not_start' do
|
186
|
+
it 'generates a NOT LIKE query with value followed by %' do
|
187
|
+
@s.name_not_start = 'Eri'
|
188
|
+
field = "#{quote_table_name("people")}.#{quote_column_name("name")}"
|
189
|
+
expect(@s.result.to_sql).to match /#{field} NOT I?LIKE 'Eri%'/
|
190
|
+
end
|
191
|
+
|
192
|
+
it "works with attribute names ending with '_start'" do
|
193
|
+
@s.new_start_not_start = 'hEy'
|
194
|
+
field = "#{quote_table_name("people")}.#{quote_column_name("new_start")}"
|
195
|
+
expect(@s.result.to_sql).to match /#{field} NOT I?LIKE 'hEy%'/
|
196
|
+
end
|
197
|
+
|
198
|
+
it "works with attribute names ending with '_end'" do
|
199
|
+
@s.stop_end_not_start = 'begin'
|
200
|
+
field = "#{quote_table_name("people")}.#{quote_column_name("stop_end")}"
|
201
|
+
expect(@s.result.to_sql).to match /#{field} NOT I?LIKE 'begin%'/
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
describe 'end' do
|
206
|
+
it 'generates a LIKE query with value preceded by %' do
|
207
|
+
@s.name_end = 'Miller'
|
208
|
+
field = "#{quote_table_name("people")}.#{quote_column_name("name")}"
|
209
|
+
expect(@s.result.to_sql).to match /#{field} I?LIKE '%Miller'/
|
210
|
+
end
|
211
|
+
|
212
|
+
it "works with attribute names ending with '_start'" do
|
213
|
+
@s.new_start_end = 'finish'
|
214
|
+
field = "#{quote_table_name("people")}.#{quote_column_name("new_start")}"
|
215
|
+
expect(@s.result.to_sql).to match /#{field} I?LIKE '%finish'/
|
216
|
+
end
|
217
|
+
|
218
|
+
it "works with attribute names ending with '_end'" do
|
219
|
+
@s.stop_end_end = 'Ending'
|
220
|
+
field = "#{quote_table_name("people")}.#{quote_column_name("stop_end")}"
|
221
|
+
expect(@s.result.to_sql).to match /#{field} I?LIKE '%Ending'/
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
describe 'not_end' do
|
226
|
+
it 'generates a NOT LIKE query with value preceded by %' do
|
227
|
+
@s.name_not_end = 'Miller'
|
228
|
+
field = "#{quote_table_name("people")}.#{quote_column_name("name")}"
|
229
|
+
expect(@s.result.to_sql).to match /#{field} NOT I?LIKE '%Miller'/
|
230
|
+
end
|
231
|
+
|
232
|
+
it "works with attribute names ending with '_start'" do
|
233
|
+
@s.new_start_not_end = 'finish'
|
234
|
+
field = "#{quote_table_name("people")}.#{quote_column_name("new_start")}"
|
235
|
+
expect(@s.result.to_sql).to match /#{field} NOT I?LIKE '%finish'/
|
236
|
+
end
|
237
|
+
|
238
|
+
it "works with attribute names ending with '_end'" do
|
239
|
+
@s.stop_end_not_end = 'Ending'
|
240
|
+
field = "#{quote_table_name("people")}.#{quote_column_name("stop_end")}"
|
241
|
+
expect(@s.result.to_sql).to match /#{field} NOT I?LIKE '%Ending'/
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
83
245
|
describe 'true' do
|
84
246
|
it 'generates an equality condition for boolean true' do
|
85
247
|
@s.awesome_true = true
|
@@ -6,7 +6,10 @@ module Ransack
|
|
6
6
|
describe '.attribute' do
|
7
7
|
it 'translate namespaced attribute like AR does' do
|
8
8
|
ar_translation = ::Namespace::Article.human_attribute_name(:title)
|
9
|
-
ransack_translation = Ransack::Translate.attribute(
|
9
|
+
ransack_translation = Ransack::Translate.attribute(
|
10
|
+
:title,
|
11
|
+
:context => ::Namespace::Article.search.context
|
12
|
+
)
|
10
13
|
expect(ransack_translation).to eq ar_translation
|
11
14
|
end
|
12
15
|
end
|