ransack 1.4.1 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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.join('_'))
106
+ assoc, klass = unpolymorphize_association(segments
107
+ .join(Ransack::Constants::UNDERSCORE))
107
108
  if found_assoc = get_association(assoc, base)
108
- base = traverse(remainder.join('_'), klass || found_assoc.klass)
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 && !base.columns_hash[segments.join('_')] &&
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(association_parts
130
- .join('_'))
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['RANSACK_FORM_BUILDER'] || '').match('SimpleForm')
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['RANSACK_FORM_BUILDER'].try(:constantize) ||
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 || 'search'
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 = [''] + association_array(options[:associations])
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, 'sort') +
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 { |k| only.include? k.sub(/_(any|all)$/, '') }
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
- [['asc', object.translate('asc')], ['desc', object.translate('desc')]]
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
- [['or', Translate.word(:any)], ['and', Translate.word(:all)]]
174
+ [
175
+ [Ransack::Constants::OR, Translate.word(:any)],
176
+ [Ransack::Constants::AND, Translate.word(:all)]
177
+ ]
166
178
  else
167
- [['and', Translate.word(:all)], ['or', Translate.word(:any)]]
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? }.join('_')
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
- "#{action.sub('search', 'attribute')
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
- '&#9650;'.freeze
15
- end
16
-
17
- def desc_arrow
18
- '&#9660;'.freeze
19
- end
20
-
21
- def non_breaking_space
22
- '&nbsp;'.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] ||= 'q'.freeze
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
- search_params = params[search.context.search_key].presence ||
69
- {}.with_indifferent_access
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
- attr_name = attribute.to_s
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
- name = (
74
- if args.size > 0 && !args.first.is_a?(Hash)
60
+ label_text =
61
+ if String === args.first
75
62
  args.shift.to_s
76
63
  else
77
- Translate.attribute(attr_name, :context => search.context)
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
- current_dir = prev_attr == attr_name ? prev_dir : nil
69
+ default_order_is_a_hash = Hash === default_order
88
70
 
89
- if current_dir
90
- new_dir = current_dir == desc ? asc : desc
91
- else
92
- new_dir = default_order || asc
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 = ['sort_link', current_dir].compact.join(' ')
97
- html_options[:class] = [css, html_options[:class]].compact.join(' ')
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
- .merge(:s => "#{attr_name} #{new_dir}")
125
+ .merge(:s => sort_params)
101
126
  options.merge!(query_hash)
102
- options_for_url = params.merge options
127
+ options_for_url = params.merge(options)
103
128
 
104
- url = if routing_proxy && respond_to?(routing_proxy)
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
- link_to(
111
- [ERB::Util.h(name), order_indicator_for(current_dir)]
112
- .compact
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 order_indicator_for(order)
123
- if order == asc
124
- asc_arrow
125
- elsif order == desc
126
- desc_arrow
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
@@ -28,7 +28,7 @@ module Ransack
28
28
  alias_method :cache_key, :collection
29
29
 
30
30
  def initialize
31
- super("Search")
31
+ super("Search".freeze)
32
32
  @singular = "search".freeze
33
33
  @plural = "searches".freeze
34
34
  @element = "search".freeze
@@ -105,7 +105,7 @@ module Ransack
105
105
  end
106
106
 
107
107
  def combinator=(val)
108
- @combinator = ['and', 'or'].detect { |v| v == val.to_s } || nil
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 'and'
183
+ when Ransack::Constants::AND
184
184
  Arel::Nodes::Grouping.new(Arel::Nodes::And.new(predicates))
185
- when 'or'
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
- ['combinator', m],
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
- ['and', 'or'].include?(combinator)
244
+ Ransack::Constants::AND_OR.include?(combinator)
245
245
  end
246
246
 
247
247
  end