ransack 1.4.1 → 1.5.0

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.
@@ -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