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
data/lib/ransack/context.rb
CHANGED
@@ -37,7 +37,7 @@ module Ransack
|
|
37
37
|
@join_type = options[:join_type] || Polyamorous::OuterJoin
|
38
38
|
@search_key = options[:search_key] || Ransack.options[:search_key]
|
39
39
|
|
40
|
-
if ::ActiveRecord::VERSION::STRING >= "4.1"
|
40
|
+
if ::ActiveRecord::VERSION::STRING >= "4.1".freeze
|
41
41
|
@base = @join_dependency.join_root
|
42
42
|
@engine = @base.base_klass.arel_engine
|
43
43
|
else
|
@@ -95,7 +95,7 @@ module Ransack
|
|
95
95
|
end
|
96
96
|
|
97
97
|
def traverse(str, base = @base)
|
98
|
-
str ||=
|
98
|
+
str ||= Ransack::Constants::EMPTY
|
99
99
|
|
100
100
|
if (segments = str.split(/_/)).size > 0
|
101
101
|
remainder = []
|
@@ -103,9 +103,13 @@ module Ransack
|
|
103
103
|
while !found_assoc && segments.size > 0 do
|
104
104
|
# Strip the _of_Model_type text from the association name, but hold
|
105
105
|
# onto it in klass, for use as the next base
|
106
|
-
assoc, klass = unpolymorphize_association(segments
|
106
|
+
assoc, klass = unpolymorphize_association(segments
|
107
|
+
.join(Ransack::Constants::UNDERSCORE))
|
107
108
|
if found_assoc = get_association(assoc, base)
|
108
|
-
base = traverse(
|
109
|
+
base = traverse(
|
110
|
+
remainder.join(
|
111
|
+
Ransack::Constants::UNDERSCORE), klass || found_assoc.klass
|
112
|
+
)
|
109
113
|
end
|
110
114
|
|
111
115
|
remainder.unshift segments.pop
|
@@ -119,15 +123,17 @@ module Ransack
|
|
119
123
|
|
120
124
|
def association_path(str, base = @base)
|
121
125
|
base = klassify(base)
|
122
|
-
str ||=
|
126
|
+
str ||= Ransack::Constants::EMPTY
|
123
127
|
path = []
|
124
128
|
segments = str.split(/_/)
|
125
129
|
association_parts = []
|
126
130
|
if (segments = str.split(/_/)).size > 0
|
127
|
-
while segments.size > 0 &&
|
131
|
+
while segments.size > 0 &&
|
132
|
+
!base.columns_hash[segments.join(Ransack::Constants::UNDERSCORE)] &&
|
128
133
|
association_parts << segments.shift do
|
129
|
-
assoc, klass = unpolymorphize_association(
|
130
|
-
|
134
|
+
assoc, klass = unpolymorphize_association(
|
135
|
+
association_parts.join(Ransack::Constants::UNDERSCORE)
|
136
|
+
)
|
131
137
|
if found_assoc = get_association(assoc, base)
|
132
138
|
path += association_parts
|
133
139
|
association_parts = []
|
@@ -136,7 +142,7 @@ module Ransack
|
|
136
142
|
end
|
137
143
|
end
|
138
144
|
|
139
|
-
path.join(
|
145
|
+
path.join(Ransack::Constants::UNDERSCORE)
|
140
146
|
end
|
141
147
|
|
142
148
|
def unpolymorphize_association(str)
|
@@ -160,15 +166,15 @@ module Ransack
|
|
160
166
|
klass.ransackable_scopes(auth_object).any? { |s| s.to_s == str }
|
161
167
|
end
|
162
168
|
|
163
|
-
def searchable_attributes(str =
|
169
|
+
def searchable_attributes(str = Ransack::Constants::EMPTY)
|
164
170
|
traverse(str).ransackable_attributes(auth_object)
|
165
171
|
end
|
166
172
|
|
167
|
-
def sortable_attributes(str =
|
173
|
+
def sortable_attributes(str = Ransack::Constants::EMPTY)
|
168
174
|
traverse(str).ransortable_attributes(auth_object)
|
169
175
|
end
|
170
176
|
|
171
|
-
def searchable_associations(str =
|
177
|
+
def searchable_associations(str = Ransack::Constants::EMPTY)
|
172
178
|
traverse(str).ransackable_associations(auth_object)
|
173
179
|
end
|
174
180
|
end
|
@@ -1,11 +1,14 @@
|
|
1
1
|
require 'action_view'
|
2
2
|
|
3
|
+
RANSACK_FORM_BUILDER = 'RANSACK_FORM_BUILDER'.freeze
|
4
|
+
|
3
5
|
require 'simple_form' if
|
4
|
-
(ENV[
|
6
|
+
(ENV[RANSACK_FORM_BUILDER] || Ransack::Constants::EMPTY)
|
7
|
+
.match('SimpleForm'.freeze)
|
5
8
|
|
6
9
|
module Ransack
|
7
10
|
module Helpers
|
8
|
-
class FormBuilder < (ENV[
|
11
|
+
class FormBuilder < (ENV[RANSACK_FORM_BUILDER].try(:constantize) ||
|
9
12
|
ActionView::Helpers::FormBuilder)
|
10
13
|
|
11
14
|
def label(method, *args, &block)
|
@@ -27,12 +30,13 @@ module Ransack
|
|
27
30
|
def attribute_select(options = nil, html_options = nil, action = nil)
|
28
31
|
options = options || {}
|
29
32
|
html_options = html_options || {}
|
30
|
-
action = action ||
|
33
|
+
action = action || Ransack::Constants::SEARCH
|
31
34
|
default = options.delete(:default)
|
32
35
|
raise ArgumentError, formbuilder_error_message(
|
33
36
|
"#{action}_select") unless object.respond_to?(:context)
|
34
37
|
options[:include_blank] = true unless options.has_key?(:include_blank)
|
35
|
-
bases = [
|
38
|
+
bases = [Ransack::Constants::EMPTY] +
|
39
|
+
association_array(options[:associations])
|
36
40
|
if bases.size > 1
|
37
41
|
collection = attribute_collection_for_bases(action, bases)
|
38
42
|
object.name ||= default if can_use_default?(
|
@@ -50,12 +54,12 @@ module Ransack
|
|
50
54
|
|
51
55
|
def sort_direction_select(options = {}, html_options = {})
|
52
56
|
raise ArgumentError, formbuilder_error_message(
|
53
|
-
'sort_direction') unless object.respond_to?(:context)
|
57
|
+
'sort_direction'.freeze) unless object.respond_to?(:context)
|
54
58
|
template_collection_select(:dir, sort_array, options, html_options)
|
55
59
|
end
|
56
60
|
|
57
61
|
def sort_select(options = {}, html_options = {})
|
58
|
-
attribute_select(options, html_options,
|
62
|
+
attribute_select(options, html_options, Ransack::Constants::SORT) +
|
59
63
|
sort_direction_select(options, html_options)
|
60
64
|
end
|
61
65
|
|
@@ -107,7 +111,7 @@ module Ransack
|
|
107
111
|
|
108
112
|
def predicate_select(options = {}, html_options = {})
|
109
113
|
options[:compounds] = true if options[:compounds].nil?
|
110
|
-
default = options.delete(:default) || 'cont'
|
114
|
+
default = options.delete(:default) || 'cont'.freeze
|
111
115
|
|
112
116
|
keys = options[:compounds] ? Predicate.names :
|
113
117
|
Predicate.names.reject { |k| k.match(/_(any|all)$/) }
|
@@ -116,7 +120,9 @@ module Ransack
|
|
116
120
|
keys = keys.select { |k| only.call(k) }
|
117
121
|
else
|
118
122
|
only = Array.wrap(only).map(&:to_s)
|
119
|
-
keys = keys.select {
|
123
|
+
keys = keys.select {
|
124
|
+
|k| only.include? k.sub(/_(any|all)$/, Ransack::Constants::EMPTY)
|
125
|
+
}
|
120
126
|
end
|
121
127
|
end
|
122
128
|
collection = keys.map { |k| [k, Translate.predicate(k)] }
|
@@ -157,14 +163,23 @@ module Ransack
|
|
157
163
|
end
|
158
164
|
|
159
165
|
def sort_array
|
160
|
-
[
|
166
|
+
[
|
167
|
+
[Ransack::Constants::ASC, object.translate(Ransack::Constants::ASC)],
|
168
|
+
[Ransack::Constants::DESC, object.translate(Ransack::Constants::DESC)]
|
169
|
+
]
|
161
170
|
end
|
162
171
|
|
163
172
|
def combinator_choices
|
164
173
|
if Nodes::Condition === object
|
165
|
-
[
|
174
|
+
[
|
175
|
+
[Ransack::Constants::OR, Translate.word(:any)],
|
176
|
+
[Ransack::Constants::AND, Translate.word(:all)]
|
177
|
+
]
|
166
178
|
else
|
167
|
-
[
|
179
|
+
[
|
180
|
+
[Ransack::Constants::AND, Translate.word(:all)],
|
181
|
+
[Ransack::Constants::OR, Translate.word(:any)]
|
182
|
+
]
|
168
183
|
end
|
169
184
|
end
|
170
185
|
|
@@ -172,7 +187,7 @@ module Ransack
|
|
172
187
|
([prefix] + association_object(obj))
|
173
188
|
.compact
|
174
189
|
.flatten
|
175
|
-
.map { |v| [prefix, v].compact.join(
|
190
|
+
.map { |v| [prefix, v].compact.join(Ransack::Constants::UNDERSCORE) }
|
176
191
|
end
|
177
192
|
|
178
193
|
def association_object(obj)
|
@@ -192,7 +207,7 @@ module Ransack
|
|
192
207
|
when Array, Hash
|
193
208
|
association_array(value, key.to_s)
|
194
209
|
else
|
195
|
-
[key.to_s, [key, value].join(
|
210
|
+
[key.to_s, [key, value].join(Ransack::Constants::UNDERSCORE)]
|
196
211
|
end
|
197
212
|
end
|
198
213
|
end
|
@@ -227,11 +242,13 @@ module Ransack
|
|
227
242
|
end
|
228
243
|
|
229
244
|
def attr_from_base_and_column(base, column)
|
230
|
-
[base, column].reject { |v| v.blank? }
|
245
|
+
[base, column].reject { |v| v.blank? }
|
246
|
+
.join(Ransack::Constants::UNDERSCORE)
|
231
247
|
end
|
232
248
|
|
233
249
|
def formbuilder_error_message(action)
|
234
|
-
"#{
|
250
|
+
"#{
|
251
|
+
action.sub(Ransack::Constants::SEARCH, Ransack::Constants::ATTRIBUTE)
|
235
252
|
} must be called inside a search FormBuilder!"
|
236
253
|
end
|
237
254
|
|
@@ -2,26 +2,6 @@ module Ransack
|
|
2
2
|
module Helpers
|
3
3
|
module FormHelper
|
4
4
|
|
5
|
-
def asc
|
6
|
-
'asc'.freeze
|
7
|
-
end
|
8
|
-
|
9
|
-
def desc
|
10
|
-
'desc'.freeze
|
11
|
-
end
|
12
|
-
|
13
|
-
def asc_arrow
|
14
|
-
'▲'.freeze
|
15
|
-
end
|
16
|
-
|
17
|
-
def desc_arrow
|
18
|
-
'▼'.freeze
|
19
|
-
end
|
20
|
-
|
21
|
-
def non_breaking_space
|
22
|
-
' '.freeze
|
23
|
-
end
|
24
|
-
|
25
5
|
def search_form_for(record, options = {}, &proc)
|
26
6
|
if record.is_a?(Ransack::Search)
|
27
7
|
search = record
|
@@ -48,13 +28,14 @@ module Ransack
|
|
48
28
|
"#{search.klass.to_s.underscore}_search",
|
49
29
|
:method => :get
|
50
30
|
}
|
51
|
-
options[:as] ||=
|
31
|
+
options[:as] ||= Ransack::Constants::DEFAULT_SEARCH_KEY
|
52
32
|
options[:html].reverse_merge!(html_options)
|
53
33
|
options[:builder] ||= FormBuilder
|
54
34
|
|
55
35
|
form_for(record, options, &proc)
|
56
36
|
end
|
57
37
|
|
38
|
+
# sort_link @q, :name, [:name, 'kind ASC'], 'Player Name'
|
58
39
|
def sort_link(search, attribute, *args)
|
59
40
|
# Extract out a routing proxy for url_for scoping later
|
60
41
|
if search.is_a?(Array)
|
@@ -65,65 +46,112 @@ module Ransack
|
|
65
46
|
raise TypeError, "First argument must be a Ransack::Search!" unless
|
66
47
|
Search === search
|
67
48
|
|
68
|
-
|
69
|
-
|
49
|
+
# This is the field that this link represents. The direction of the sort icon (up/down arrow) will
|
50
|
+
# depend on the sort status of this field
|
51
|
+
field_name = attribute.to_s
|
70
52
|
|
71
|
-
|
53
|
+
# Determine the fields we want to sort on
|
54
|
+
sort_fields = if Array === args.first
|
55
|
+
args.shift
|
56
|
+
else
|
57
|
+
Array(field_name)
|
58
|
+
end
|
72
59
|
|
73
|
-
|
74
|
-
if
|
60
|
+
label_text =
|
61
|
+
if String === args.first
|
75
62
|
args.shift.to_s
|
76
63
|
else
|
77
|
-
Translate.attribute(
|
64
|
+
Translate.attribute(field_name, :context => search.context)
|
78
65
|
end
|
79
|
-
)
|
80
|
-
|
81
|
-
if existing_sort = search.sorts.detect { |s| s.name == attr_name }
|
82
|
-
prev_attr, prev_dir = existing_sort.name, existing_sort.dir
|
83
|
-
end
|
84
66
|
|
85
67
|
options = args.first.is_a?(Hash) ? args.shift.dup : {}
|
86
68
|
default_order = options.delete :default_order
|
87
|
-
|
69
|
+
default_order_is_a_hash = Hash === default_order
|
88
70
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
71
|
+
# If the default order is a hash of fields, duplicate it and let us access it with strings or symbols
|
72
|
+
default_order = default_order.dup.with_indifferent_access if
|
73
|
+
default_order_is_a_hash
|
74
|
+
|
75
|
+
search_params = params[search.context.search_key].presence ||
|
76
|
+
{}.with_indifferent_access
|
77
|
+
|
78
|
+
# Find the current direction (if there is one) of the primary sort field
|
79
|
+
if existing_sort = search.sorts.detect { |s| s.name == field_name }
|
80
|
+
field_current_dir = existing_sort.dir
|
93
81
|
end
|
94
82
|
|
83
|
+
sort_params = []
|
84
|
+
|
85
|
+
Array(sort_fields).each do |sort_field|
|
86
|
+
attr_name, new_dir = sort_field.to_s.downcase.split(/\s+/)
|
87
|
+
current_dir = nil
|
88
|
+
|
89
|
+
# if the user didn't specify the sort direction, detect the previous
|
90
|
+
# sort direction on this field and reverse it
|
91
|
+
if Ransack::Constants::ASC_DESC.none? { |d| d == new_dir }
|
92
|
+
if existing_sort = search.sorts.detect { |s| s.name == attr_name }
|
93
|
+
current_dir = existing_sort.dir
|
94
|
+
end
|
95
|
+
|
96
|
+
new_dir =
|
97
|
+
if current_dir
|
98
|
+
if current_dir == Ransack::Constants::DESC
|
99
|
+
Ransack::Constants::ASC
|
100
|
+
else
|
101
|
+
Ransack::Constants::DESC
|
102
|
+
end
|
103
|
+
elsif default_order_is_a_hash
|
104
|
+
default_order[attr_name] || Ransack::Constants::ASC
|
105
|
+
else
|
106
|
+
default_order || Ransack::Constants::ASC
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
sort_params << "#{attr_name} #{new_dir}"
|
111
|
+
end
|
112
|
+
|
113
|
+
# if there is only one sort parameter, remove it from the array and just
|
114
|
+
# use the string as the parameter
|
115
|
+
sort_params = sort_params.first if sort_params.size == 1
|
116
|
+
|
95
117
|
html_options = args.first.is_a?(Hash) ? args.shift.dup : {}
|
96
|
-
css = [
|
97
|
-
|
118
|
+
css = [Ransack::Constants::SORT_LINK, field_current_dir]
|
119
|
+
.compact.join(Ransack::Constants::SPACE)
|
120
|
+
html_options[:class] = [css, html_options[:class]]
|
121
|
+
.compact.join(Ransack::Constants::SPACE)
|
122
|
+
|
98
123
|
query_hash = {}
|
99
124
|
query_hash[search.context.search_key] = search_params
|
100
|
-
|
125
|
+
.merge(:s => sort_params)
|
101
126
|
options.merge!(query_hash)
|
102
|
-
options_for_url = params.merge
|
127
|
+
options_for_url = params.merge(options)
|
103
128
|
|
104
|
-
url =
|
129
|
+
url =
|
130
|
+
if routing_proxy && respond_to?(routing_proxy)
|
105
131
|
send(routing_proxy).url_for(options_for_url)
|
106
132
|
else
|
107
133
|
url_for(options_for_url)
|
108
134
|
end
|
109
135
|
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
.join(non_breaking_space)
|
114
|
-
.html_safe,
|
115
|
-
url,
|
116
|
-
html_options
|
117
|
-
)
|
136
|
+
name = link_name(label_text, field_current_dir)
|
137
|
+
|
138
|
+
link_to(name, url, html_options)
|
118
139
|
end
|
119
140
|
|
120
141
|
private
|
121
142
|
|
122
|
-
def
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
143
|
+
def link_name(label_text, dir)
|
144
|
+
[ERB::Util.h(label_text), order_indicator_for(dir)]
|
145
|
+
.compact
|
146
|
+
.join(Ransack::Constants::NON_BREAKING_SPACE)
|
147
|
+
.html_safe
|
148
|
+
end
|
149
|
+
|
150
|
+
def order_indicator_for(dir)
|
151
|
+
if dir == Ransack::Constants::ASC
|
152
|
+
Ransack::Constants::ASC_ARROW
|
153
|
+
elsif dir == Ransack::Constants::DESC
|
154
|
+
Ransack::Constants::DESC_ARROW
|
127
155
|
else
|
128
156
|
nil
|
129
157
|
end
|
data/lib/ransack/naming.rb
CHANGED
@@ -105,7 +105,7 @@ module Ransack
|
|
105
105
|
end
|
106
106
|
|
107
107
|
def combinator=(val)
|
108
|
-
@combinator =
|
108
|
+
@combinator = Ransack::Constants::AND_OR.detect { |v| v == val.to_s } || nil
|
109
109
|
end
|
110
110
|
alias :m= :combinator=
|
111
111
|
alias :m :combinator
|
@@ -180,9 +180,9 @@ module Ransack
|
|
180
180
|
|
181
181
|
if predicates.size > 1
|
182
182
|
case combinator
|
183
|
-
when
|
183
|
+
when Ransack::Constants::AND
|
184
184
|
Arel::Nodes::Grouping.new(Arel::Nodes::And.new(predicates))
|
185
|
-
when
|
185
|
+
when Ransack::Constants::OR
|
186
186
|
predicates.inject(&:or)
|
187
187
|
end
|
188
188
|
else
|
@@ -226,14 +226,14 @@ module Ransack
|
|
226
226
|
|
227
227
|
def inspect
|
228
228
|
data = [
|
229
|
-
['attributes', a.try(:map, &:name)],
|
230
|
-
['predicate', p],
|
231
|
-
[
|
232
|
-
['values', v.try(:map, &:value)]
|
229
|
+
['attributes'.freeze, a.try(:map, &:name)],
|
230
|
+
['predicate'.freeze, p],
|
231
|
+
[Ransack::Constants::COMBINATOR, m],
|
232
|
+
['values'.freeze, v.try(:map, &:value)]
|
233
233
|
]
|
234
234
|
.reject { |e| e[1].blank? }
|
235
235
|
.map { |v| "#{v[0]}: #{v[1]}" }
|
236
|
-
.join(
|
236
|
+
.join(Ransack::Constants::COMMA_SPACE)
|
237
237
|
"Condition <#{data}>"
|
238
238
|
end
|
239
239
|
|
@@ -241,7 +241,7 @@ module Ransack
|
|
241
241
|
|
242
242
|
def valid_combinator?
|
243
243
|
attributes.size < 2 ||
|
244
|
-
|
244
|
+
Ransack::Constants::AND_OR.include?(combinator)
|
245
245
|
end
|
246
246
|
|
247
247
|
end
|