ransack_ui 1.1.0 → 1.2.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.
checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ NzUxNDFiZWU1YTllYzkwMTNjZmU2ZTJmZmExOTVlMzI0OWQ4YzQ1Yg==
5
+ data.tar.gz: !binary |-
6
+ NWM3YzRiZWVjMGZjZGY3NTQ2ODg2NzlhZmM2NzlhYzYyNDc0M2Y1Ng==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ OGVhZGIxMDMxZjc3YWQ3YzYwZDk2MjIyOTJiOTZjZmQ2NWQ3MGI5MTQxNTcy
10
+ YjMzNDYwM2U4MDE4NDA1ZDU5OGI0NGNkZGMwMGE2NGNiZGZkODdkZTdlZTc4
11
+ MTEzOGVlOWUzMjc1ODNjZDUzNDFlN2U2NGVkYjI2MzgxMDZiYmQ=
12
+ data.tar.gz: !binary |-
13
+ MDgxY2Y0YzllYTQyMDM1ZGM0NTZkNzI5YWExYTVjNGM2NDhmZDA4NmE4ZjVl
14
+ OTRiMzgzMjEzNjFjYTc1ZWY0MzAzYzIzZjQ0MGZhOGM4NDBjY2NiMGQ2MDVm
15
+ YjdjOTUzOTYxYTZkY2M4MGI0NjBhMjc4ODJhNjRlYTRhMjM5NGE=
@@ -22,61 +22,57 @@
22
22
  # show spinner and disable the form when the search is underway
23
23
  el.find("form input:submit").click $.proxy(@form_submit, this)
24
24
 
25
- # Fire change event for any existing selects.
26
- el.find(".filters select").change()
25
+ # Fire change event for any existing attribute selects,
26
+ # set initialize to true so that existing queries are not cleared.
27
+ el.find(".filters select.ransack_attribute").each (i, el) =>
28
+ @attribute_changed({currentTarget: el}, true)
27
29
 
28
- attribute_changed: (e) ->
30
+ attribute_changed: (e, initialize = false) ->
29
31
  target = $(e.currentTarget)
30
- selected = target.find('option:selected')
31
- column_type = selected.data('type')
32
+ selected_attribute = target.find('option:selected')
33
+ column_type = selected_attribute.data('type')
32
34
 
33
35
  base_id = target.attr('id').slice(0, -8)
34
36
  predicate_select = @element.find("select##{base_id}p")
35
37
  available = predicate_select.data['predicates']
36
38
  query_input = $("input##{base_id}v_0_value")
39
+ multi_id = query_input.attr('id') + '_multi'
40
+ multi_input = @element.find("##{multi_id}")
41
+ query_select2_id = "#s2id_#{base_id}v_0_value"
42
+ query_select2 = @element.find(query_select2_id)
43
+ predicate_select2_id = "#s2id_#{base_id}p"
44
+ predicate_select2 = @element.find(predicate_select2_id)
37
45
 
38
46
  # Initialize datepicker if column is date/datetime/time
39
47
  $.proxy(@init_datetimepicker, this)(base_id)
40
48
 
49
+ # Clear input value unless this is the first run
50
+ unless initialize
51
+ query_input.val('')
52
+
53
+ # Destroy any query select2 inputs on attribute change and clear input
54
+ if query_select2.length
55
+ query_input.select2('destroy')
56
+
57
+ # Destroy any multi-inputs on attribute change and clear input
58
+ if multi_input.length
59
+ @destroy_multi_input(multi_input, selected_attribute.val())
60
+
41
61
  # Handle association columns with AJAX autocomplete
42
- if selected.data('ajax-url') and Select2?
43
- controller = selected.data('controller')
62
+ if selected_attribute.data('ajax-url') and Select2?
44
63
  @set_option_predicates(base_id, available, column_type)
45
64
 
46
- # Set up Select2 for query input
47
- query_input.val('')
48
- query_input.select2
49
- placeholder: "Search #{selected.data('ajax-entity')}"
50
- minimumInputLength: 1
51
- allowClear: true
52
- ajax:
53
- url: selected.data('ajax-url')
54
- dataType: 'json'
55
- type: selected.data('ajax-type')
56
- data: (query, page) ->
57
- obj = {}
58
- obj[selected.data('ajax-key')] = query
59
- obj
60
- results: (data, page) ->
61
- {results: $.map(data, (text, id) -> {id: id, text: text}) }
62
-
63
65
  # Handle columns with options detected from validates :inclusion
64
- else if selected.data('select-options') and Select2?
65
- @set_option_predicates(base_id, available, column_type)
66
- query_input.val('')
67
- query_input.select2
68
- data: selected.data('select-options')
69
- placeholder: "Please select a #{selected.val()}"
70
- allowClear: true
66
+ else if selected_attribute.data('select-options') and Select2?
67
+ @set_option_predicates(base_id, available, column_type, true)
71
68
 
72
69
  # Handle regular columns
73
70
  else
74
71
  if Select2?
75
- predicate_select2 = @element.find("#s2id_#{base_id}p")
76
72
  predicate_select2.select2("enable")
77
73
 
78
74
  # If Select2 is on query input, remove and set defaults
79
- if (query_select2 = @element.find("#s2id_#{base_id}v_0_value")).length > 0
75
+ if query_select2.length > 0
80
76
  query_input.select2('destroy')
81
77
  query_input.val('')
82
78
  previous_val = ''
@@ -109,8 +105,8 @@
109
105
  if Select2?
110
106
  predicate_select.select2('val', previous_val)
111
107
 
112
- # Run predicate_changed callback
113
- predicate_select.change()
108
+ # Run predicate_changed callback
109
+ predicate_select.change()
114
110
 
115
111
  return true
116
112
 
@@ -124,86 +120,111 @@
124
120
  attribute_select = @element.find("select##{base_id}a_0_name")
125
121
  selected_attribute = attribute_select.find('option:selected')
126
122
 
123
+ query_select2_id = "#s2id_#{base_id}v_0_value"
124
+ query_select2 = @element.find(query_select2_id)
125
+ query_select2_multi_id = "#s2id_#{base_id}v_0_value_multi"
126
+ query_select2_multi = @element.find(query_select2_multi_id)
127
+
128
+ no_query_predicates = ["true", "false", "blank", "present", "null", "not_null"]
129
+
127
130
  # We need to use a dummy input to handle multiple terms
128
131
  multi_id = query_input.attr('id') + '_multi'
129
132
  multi_input = @element.find("##{multi_id}")
130
133
 
134
+ # If query was previously hidden, clear query input
135
+ if query_select2.length == 0 && multi_input.length == 0 && query_input.is(":hidden")
136
+ query_input.val('')
137
+
138
+ # Hide query input when not needed
139
+ if p in no_query_predicates
140
+ # If Select2 is on query input, remove and set defaults
141
+ if Select2? && query_select2.length
142
+ query_input.select2('destroy')
143
+
144
+ query_input.val("true")
145
+ query_input.hide()
146
+ query_input.parent().find('.ui-datepicker-trigger').hide()
147
+
131
148
  if Select2?
132
149
  # Turn query input into Select2 tag list when query accepts multiple values
133
150
  if p in ["in", "not_in"] || p.match(/_(all|any)$/)
134
- # If Select2 is on query input, remove and set defaults
135
- if @element.find("#s2id_#{base_id}v_0_value").length
136
- query_input.select2('destroy')
137
-
138
151
  # Add dummy 'multi' input for select2 if not already added
139
- if multi_input.length == 0
152
+ if multi_input.length == 0 && query_select2_multi.length == 0
140
153
  # Set up multi-query input with fixed options, if present
141
- query_input.val('')
142
- options = selected_attribute.data('select-options')
143
- @setup_multi_query_input(target, query_input, multi_id, options)
154
+ @setup_multi_query_input(target, query_input, multi_id, selected_attribute)
155
+
156
+ # If Select2 is on query input, remove and set defaults
157
+ if query_select2.length
158
+ query_input.select2('destroy').hide()
144
159
 
145
160
  return
146
161
 
147
- else
162
+ else
163
+ # Otherwise, remove Select2 from multi-query input, and remove input.
148
164
  if multi_input.length
149
- # Otherwise, remove Select2 from multi-query input, and remove input.
150
- multi_input.select2('destroy').remove()
151
- # Also remove all extra inputs
152
- base_name = multi_input.data('base-name')
153
- inputs = @element.find("input[name^=\"#{base_name}\"]")
154
- inputs = inputs.slice(1)
155
- inputs.remove()
156
-
157
- # If no fixed options, show query input
158
- query_input.val('').show()
159
-
160
- # Handle fixed options - set up Select2 for single values if not already present
161
- if selected_attribute.data('select-options')
162
- if (query_select2 = @element.find("#s2id_#{base_id}v_0_value")).length == 0
163
- query_input.select2
164
- data: selected_attribute.data('select-options')
165
- placeholder: "Please select a #{selected_attribute.val()}"
166
- allowClear: true
165
+ # Save label data from first value
166
+ if multi_input.select2('data') && multi_input.select2('data').length
167
+ multi_input_data = multi_input.select2('data').first()
168
+ Ransack.value_field_labels[selected_attribute.val()] ||= {}
169
+ Ransack.value_field_labels[selected_attribute.val()][multi_input_data.id] = multi_input_data.text
167
170
 
168
- return
171
+ @destroy_multi_input(multi_input, selected_attribute.val())
169
172
 
173
+ if p not in no_query_predicates
174
+ query_input.show()
170
175
 
171
- # Hide query input when not needed
172
- if p in ["true", "false", "blank", "present", "null", "not_null"]
173
- # If Select2 is on query input, remove and set defaults
174
- if Select2? && @element.find("#s2id_#{base_id}v_0_value").length
175
- query_input.select2('destroy')
176
- query_input.val('')
176
+ # Handle association columns with AJAX autocomplete
177
+ if selected_attribute.data('ajax-url')
178
+ if query_select2.length
179
+ query_input.hide()
180
+ else
181
+ @setup_select2_association(query_input, selected_attribute)
182
+
183
+ # Handle fixed options - set up Select2 for single values if not already present
184
+ if selected_attribute.data('select-options')
185
+ if query_select2.length
186
+ query_input.hide()
187
+ else
188
+ @setup_select2_options(query_input, selected_attribute)
177
189
 
178
- query_input.val("true")
179
- query_input.hide()
180
- query_input.parent().find('.ui-datepicker-trigger').hide()
181
190
  # Otherwise, reset query input and show datepicker trigger if present
182
- else
183
- unless query_input.is(":visible")
184
- query_input.val("")
185
- query_input.show()
186
- query_input.parent().find('.ui-datepicker-trigger').show()
191
+ if p not in no_query_predicates
192
+ return if selected_attribute.data('select-options')
193
+
194
+ # Don't show query input if ajax auto complete is present on selected attribute
195
+ unless p in ['eq', 'not_eq'] and selected_attribute.data('ajax-url')
196
+ unless query_input.is(":visible")
197
+ query_input.val('')
198
+ query_input.show()
199
+ query_input.parent().find('.ui-datepicker-trigger').show()
187
200
 
188
201
 
189
- # Dsiables predicate choices and sets it to 'eq'
190
- set_option_predicates: (base_id, available, column_type) ->
202
+ # Disables predicate choices and sets it to 'eq'
203
+ set_option_predicates: (base_id, available_predicates, column_type, include_number_predicates = false) ->
191
204
  predicate_select = @element.find("select##{base_id}p")
205
+ previous_val = predicate_select.val()
192
206
 
193
207
  # Remove all predicates, and add any supported predicates
194
208
  predicate_select.find('option').each (i, o) -> $(o).remove()
195
209
 
196
- $.each available, (i, p) =>
210
+ allowed_predicates = $.merge([], Ransack.option_predicates)
211
+
212
+ # Include number predicates if the option was given.
213
+ # For example, a integer column will have fixed select options,
214
+ # but will also allow less than and greater than.
215
+ if column_type in ['integer', 'float', 'decimal'] && include_number_predicates
216
+ allowed_predicates = allowed_predicates.concat(Ransack.type_predicates[column_type] || [])
217
+
218
+ $.each available_predicates, (i, p) =>
197
219
  [predicate, label] = [p[0], p[1]]
198
220
 
199
- if predicate in Ransack.option_predicates
221
+ if predicate in allowed_predicates
200
222
  # Get alternative predicate label depending on column type
201
223
  label = @alt_predicate_label_or_default(predicate, column_type, label)
202
224
  predicate_select.append $("<option value=#{predicate}>#{label}</option>")
203
225
 
204
- if Select2?
205
- predicate_select2 = @element.find("#s2id_#{base_id}p")
206
- predicate_select2.select2('val', 'eq')
226
+ # Select first predicate if current selection is invalid
227
+ predicate_select.select2('val', previous_val)
207
228
 
208
229
  # Attempts to find a predicate translation for the specific column type,
209
230
  # or returns the default label.
@@ -246,17 +267,20 @@
246
267
  $.each values, (i, v) =>
247
268
  @add_query_input(target, base_name, i + 1, v)
248
269
 
249
- setup_multi_query_input: (predicate_el, query_el, multi_id, options) ->
270
+ setup_multi_query_input: (predicate_el, query_input, multi_id, selected_attribute) ->
250
271
  base_name = predicate_el.attr('name').slice(0, -3) + '[v]'
251
- query_el.after(
272
+ base_id = predicate_el.attr('id').slice(0, -1)
273
+ query_input.after(
252
274
  $('<input class="ransack_query_multi" id="' + multi_id + '" ' +
253
- 'style="width:' + (query_el.width() * 2) + 'px;" ' +
275
+ 'style="width:' + (query_input.width() * 2) + 'px;" ' +
254
276
  'data-base-name="' + base_name + '" />'))
255
277
 
278
+ query_select2_id = "#s2id_#{base_id}v_0_value"
279
+ query_select2 = @element.find(query_select2_id)
280
+
256
281
  # Fetch all existing values
257
282
  inputs = @element.find("input[name^=\"#{base_name}\"]")
258
- values = $.map inputs, (el) ->
259
- el.value
283
+ values = $.map inputs, (el) -> el.value
260
284
  # Hide all query inputs
261
285
  inputs.hide()
262
286
 
@@ -264,37 +288,102 @@
264
288
  # Find newly created input and setup Select2
265
289
  multi_query = @element.find('#' + multi_id)
266
290
 
267
- if options?
268
- # Setup Select2 with fixed options
269
- multi_query.select2
270
- data: options
271
- multiple: true
272
- tokenSeparators: [',']
273
- formatNoMatches:
274
- if options
275
- (t) -> "No matches found."
276
- else
277
- (t) -> "Add a search term"
278
- initSelection: (element, callback) ->
279
- data = []
280
- unless element.val().trim() == ""
281
- $(element.val().split(",")).each ->
282
- data.push {id: this, text: this}
283
- callback(data)
291
+ # Handle association columns with AJAX autocomplete
292
+ if selected_attribute.data('ajax-url')
293
+ # Set label to single association label, if anything was selected
294
+ if query_select2.length && query_input.select2('data')
295
+ query_input_data = query_input.select2('data')
296
+ Ransack.value_field_labels[selected_attribute.val()] ||= {}
297
+ Ransack.value_field_labels[selected_attribute.val()][query_input_data.id] = query_input_data.text
298
+
299
+ @setup_select2_association(multi_query, selected_attribute, true)
300
+
284
301
  else
285
- # Setup Select2 with tagging support (can create options)
286
- multi_query.select2
287
- tags: []
288
- tokenSeparators: [',']
289
- formatNoMatches: (t) ->
290
- "Add a search term"
302
+ if selected_attribute.data('select-options')
303
+ # Setup Select2 with fixed multiple options
304
+ @setup_select2_options(multi_query, selected_attribute, true)
305
+
306
+ else
307
+ # Setup Select2 with tagging support (can create options)
308
+ multi_query.select2
309
+ tags: []
310
+ tokenSeparators: [',']
311
+ formatNoMatches: (t) ->
312
+ "Add a search term"
291
313
 
292
314
  multi_query.select2('val', values)
293
315
 
316
+ setup_select2_association: (query_input, selected_attribute, multiple = false) ->
317
+ selected_attribute_val = selected_attribute.val()
318
+ # Set up Select2 for query input
319
+ query_input.select2
320
+ placeholder: "Search #{selected_attribute.data('ajax-entity')}"
321
+ minimumInputLength: 1
322
+ allowClear: true
323
+ multiple: multiple
324
+ ajax:
325
+ url: selected_attribute.data('ajax-url')
326
+ dataType: 'json'
327
+ type: selected_attribute.data('ajax-type')
328
+ data: (query, page) ->
329
+ obj = {}
330
+ obj[selected_attribute.data('ajax-key')] = query
331
+ obj
332
+ results: (data, page) ->
333
+ {results: $.map(data, (text, id) -> {id: id, text: text}) }
334
+ initSelection: (element, callback) ->
335
+ data = []
336
+ unless element.val().trim() == ""
337
+ $(element.val().split(",")).each (i, val) ->
338
+ label = Ransack.value_field_labels[selected_attribute_val]?[val]
339
+ if label
340
+ data.push {id: val, text: label}
341
+ else
342
+ data.push {id: val, text: val}
343
+ if data.length
344
+ callback(multiple and data or data[0])
345
+ else
346
+ # If no label could be found, clear value
347
+ element.select2('val', '')
348
+
349
+ setup_select2_options: (query_input, selected_attribute, multiple = false) ->
350
+ query_input.select2
351
+ data: selected_attribute.data('select-options')
352
+ placeholder: "Please select a #{selected_attribute.text()}"
353
+ allowClear: true
354
+ multiple: multiple
355
+ tokenSeparators: [',']
356
+ formatNoMatches:
357
+ if selected_attribute.data('select-options')
358
+ (t) -> "No matches found."
359
+ else
360
+ (t) -> "Add a search term"
361
+ initSelection: (element, callback) ->
362
+ data = []
363
+ unless element.val().trim() == ""
364
+ $(element.val().split(",")).each (i, val) ->
365
+ selected_attribute.data('select-options').each (option, i) ->
366
+ if option.id == val
367
+ data.push {id: option.id, text: option.text}
368
+ return false # Break out of inner each loop
369
+
370
+ if data.length
371
+ callback(multiple and data or data[0])
372
+ else
373
+ element.select2('val', '')
374
+
294
375
  add_query_input: (base_input, base_name, id, value) ->
295
376
  base_input.after $('<input name="'+base_name+'['+id+'][value]" '+
296
377
  'value="'+value+'" style="display:none;" />')
297
378
 
379
+ destroy_multi_input: (multi_input, selected_attribute_val) ->
380
+ multi_input.select2('destroy').remove()
381
+ # Also remove all extra inputs
382
+ base_name = multi_input.data('base-name')
383
+ inputs = @element.find("input[name^=\"#{base_name}\"]")
384
+ inputs = inputs.slice(1)
385
+ inputs.remove()
386
+
298
387
  form_submit: (e) ->
299
388
  @element.css({ opacity: 0.4 })
300
389
  true
@@ -330,7 +419,7 @@
330
419
 
331
420
  store_initial_predicates: (container) ->
332
421
  # Store current predicates in data attribute
333
- predicate_select = container.find('select.ransack_predicate:first')
422
+ predicate_select = container.find('select.ransack_predicate').first()
334
423
  unless predicate_select.data['predicates']
335
424
  predicates = []
336
425
  predicate_select.find('option').each (i, o) ->
@@ -349,8 +438,16 @@
349
438
  placeholder: "Select a Field"
350
439
  allowClear: true
351
440
  formatSelection: (object, container) ->
441
+ # If initializing and element is not present,
442
+ # search for option element in original select tag
443
+ if !object.element
444
+ this.element.find('option').each (i, option) ->
445
+ if option.value == object.id
446
+ object.element = option
447
+ return false
448
+
352
449
  # Return 'Model: field' unless column is on root model
353
- if !object.element || $(object.element).data('root-model')
450
+ if $(object.element).data('root-model')
354
451
  object.text
355
452
  else
356
453
  group_label = $(object.element).parent().attr('label')
@@ -372,7 +469,7 @@
372
469
  init_datetimepicker: (base_id) ->
373
470
  if $.ui?.timepicker?
374
471
  query_input = @element.find("input##{base_id}v_0_value")
375
- selected_attr = @element.find("select##{base_id}a_0_name option:selected")
472
+ selected_attribute = @element.find("select##{base_id}a_0_name option:selected")
376
473
 
377
474
  # Clear any datepicker from query input first
378
475
  query_input.datepicker('destroy')
@@ -388,7 +485,7 @@
388
485
  onClose: (date) -> $(this).val(date)
389
486
 
390
487
  # Show datepicker button for dates
391
- switch selected_attr.data('type')
488
+ switch selected_attribute.data('type')
392
489
  when "date"
393
490
  query_input.datepicker(datepicker_options)
394
491
  when "datetime"
@@ -1,9 +1,10 @@
1
1
  <%= search_form_for @ransack_search, :url => (options[:url] || url_for(:action => :index)),
2
2
  :html => {:method => :get, :class => "ransack_search"}, :remote => !!options[:remote] do |f| %>
3
3
 
4
- <% javascript_tag do %>
4
+ <%= javascript_tag do %>
5
5
  if (window.Ransack == null) { window.Ransack = {}; }
6
- Ransack.alt_predicates_i18n = #{I18n.translate(:"ransack.predicates.alt", :default => {}).to_json}
6
+ Ransack.alt_predicates_i18n = <%= I18n.translate(:"ransack.predicates.alt", :default => {}).to_json.html_safe %>;
7
+ Ransack.value_field_labels = <%= f.labels_for_value_fields.to_json.html_safe %>
7
8
  <% end %>
8
9
 
9
10
  <div class="row">
@@ -21,7 +22,7 @@
21
22
  <div class="pull-right">
22
23
  <%= hidden_field_tag :distinct, '1' %>
23
24
  <%= hidden_field_tag :page, '1' %>
24
- <%= f.submit t('ransack.submit'), :class => 'btn btn-primary btn-large' %>
25
+ <%= f.submit t('ransack.submit'), :class => 'btn btn-primary btn-large submit-search' %>
25
26
  </div>
26
27
  </div>
27
28
  </div>
@@ -3,6 +3,9 @@ require 'ransack/helpers/form_builder'
3
3
  module Ransack
4
4
  module Helpers
5
5
  FormBuilder.class_eval do
6
+ cattr_accessor :cached_searchable_attributes_for_base
7
+ self.cached_searchable_attributes_for_base = {}
8
+
6
9
  def attribute_select(options = {}, html_options = {})
7
10
  raise ArgumentError, "attribute_select must be called inside a search FormBuilder!" unless object.respond_to?(:context)
8
11
  options[:include_blank] = true unless options.has_key?(:include_blank)
@@ -58,6 +61,44 @@ module Ransack
58
61
  end
59
62
  end
60
63
 
64
+ def labels_for_value_fields
65
+ labels = {}
66
+
67
+ object.groupings.each do |grouping|
68
+ grouping.conditions.each do |condition|
69
+ condition.values.each do |value|
70
+ # If value is present, and the attribute is an association,
71
+ # load the selected record and include the record name as a data attribute
72
+ if value.value.present?
73
+ condition_attributes = condition.attributes
74
+ if condition_attributes.any?
75
+ attribute = condition_attributes.first.name
76
+ klass_name = foreign_klass_for_attribute(attribute)
77
+
78
+ if klass_name
79
+ klass = klass_name.constantize
80
+
81
+ value_object = klass.find_by_id(value.value)
82
+ if value_object
83
+ labels[attribute] ||= {}
84
+
85
+ if value_object.respond_to? :full_name
86
+ labels[attribute][value.value] = value_object.full_name
87
+ elsif value_object.respond_to? :name
88
+ labels[attribute][value.value] = value_object.name
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
97
+
98
+ labels
99
+ end
100
+
101
+
61
102
  def predicate_keys(options)
62
103
  keys = options[:compounds] ? Predicate.names : Predicate.names.reject {|k| k.match(/_(any|all)$/)}
63
104
  if only = options[:only]
@@ -101,9 +142,6 @@ module Ransack
101
142
 
102
143
  def attribute_collection_for_base(base)
103
144
  klass = object.context.traverse(base)
104
- foreign_keys = klass.reflect_on_all_associations.select(&:belongs_to?).
105
- map_to({}) {|r, h| h[r.foreign_key.to_sym] = r.class_name }
106
-
107
145
  ajax_options = Ransack.options[:ajax_options] || {}
108
146
 
109
147
  # Detect any inclusion validators to build list of options for a column
@@ -112,41 +150,36 @@ module Ransack
112
150
  v.attributes.each do |a|
113
151
  # Try to translate options from activerecord.attribute_options.<model>.<attribute>
114
152
  hash[a.to_s] = v.send(:delimiter).each_with_object({}) do |o, options|
115
- options[o] = I18n.translate("activerecord.attribute_options.#{klass.to_s.downcase}.#{a}.#{o}", :default => o)
153
+ options[o.to_s] = I18n.translate("activerecord.attribute_options.#{klass.to_s.downcase}.#{a}.#{o}", :default => o.to_s.titleize)
116
154
  end
117
155
  end
118
156
  end
119
157
  end
120
158
 
121
- object.context.searchable_attributes(base).map do |c, type|
122
- # Don't show 'id' column for base model
123
- next nil if base.blank? && c == 'id'
159
+ if klass.respond_to?(:ransack_column_select_options)
160
+ column_select_options.merge!(klass.ransack_column_select_options)
161
+ end
124
162
 
125
- attribute = attr_from_base_and_column(base, c)
126
- attribute_label = Translate.attribute(attribute, :context => object.context)
163
+ searchable_attributes_for_base(base).map do |attribute_data|
164
+ column = attribute_data[:column]
127
165
 
128
- # Set model name as label for 'id' column on that model's table.
129
- if c == 'id'
130
- foreign_klass = object.context.traverse(base).model_name
131
- # Check that model can autocomplete. If not, skip this id column.
132
- next nil unless foreign_klass.constantize._ransack_can_autocomplete
133
- attribute_label = I18n.translate(foreign_klass, :default => foreign_klass)
134
- else
135
- foreign_klass = foreign_keys[c.to_sym]
136
- end
166
+ html_options = {}
137
167
 
138
168
  # Add column type as data attribute
139
- html_options = {:'data-type' => type}
169
+ html_options[:'data-type'] = attribute_data[:type]
140
170
  # Set 'base' attribute if attribute is on base model
141
171
  html_options[:'data-root-model'] = true if base.blank?
172
+
142
173
  # Set column options if detected from inclusion validator
143
- if column_select_options[c]
174
+ if column_select_options[column]
144
175
  # Format options as an array of hashes with id and text columns, for Select2
145
- html_options[:'data-select-options'] = column_select_options[c].map {|id, text|
176
+ html_options[:'data-select-options'] = column_select_options[column].map {|id, text|
146
177
  {:id => id, :text => text}
147
178
  }.to_json
148
179
  end
149
180
 
181
+ foreign_klass = attribute_data[:foreign_klass]
182
+
150
183
  if foreign_klass
151
184
  # If field is a foreign key, set up 'data-ajax-*' attributes for auto-complete
152
185
  controller = foreign_klass.tableize
@@ -161,15 +194,66 @@ module Ransack
161
194
  end
162
195
 
163
196
  [
164
- attribute_label,
165
- attribute,
197
+ attribute_data[:label],
198
+ attribute_data[:attribute],
166
199
  html_options
167
200
  ]
168
- end.compact
201
+ end
169
202
  rescue UntraversableAssociationError => e
170
203
  nil
171
204
  end
172
- end
173
205
 
206
+
207
+ private
208
+
209
+ def searchable_attributes_for_base(base)
210
+ cache_prefix = object.context.klass.table_name
211
+ cache_key = base.blank? ? cache_prefix : [cache_prefix, base].join('_')
212
+
213
+ self.class.cached_searchable_attributes_for_base[cache_key] ||= object.context.searchable_attributes(base).map do |column, type|
214
+ klass = object.context.traverse(base)
215
+ foreign_keys = klass.reflect_on_all_associations.select(&:belongs_to?).
216
+ each_with_object({}) {|r, h| h[r.foreign_key.to_sym] = r.class_name }
217
+
218
+ # Don't show 'id' column for base model
219
+ next nil if base.blank? && column == 'id'
220
+
221
+ attribute = attr_from_base_and_column(base, column)
222
+ attribute_label = Translate.attribute(attribute, :context => object.context)
223
+
224
+ # Set model name as label for 'id' column on that model's table.
225
+ if column == 'id'
226
+ foreign_klass = object.context.traverse(base).model_name
227
+ # Check that model can autocomplete. If not, skip this id column.
228
+ next nil unless foreign_klass.constantize._ransack_can_autocomplete
229
+ attribute_label = I18n.translate(foreign_klass, :default => foreign_klass)
230
+ else
231
+ foreign_klass = foreign_keys[column.to_sym]
232
+ end
233
+
234
+ attribute_data = {
235
+ label: attribute_label,
236
+ type: type,
237
+ column: column,
238
+ attribute: attribute
239
+ }
240
+ attribute_data[:foreign_klass] = foreign_klass if foreign_klass
241
+ attribute_data
242
+ end.compact
243
+ end
244
+
245
+ def foreign_klass_for_attribute(attribute)
246
+ associations = object.context.klass.ransackable_associations
247
+ bases = [''] + association_array(associations)
248
+
249
+ bases.each do |base|
250
+ searchable_attributes_for_base(base).each do |attribute_data|
251
+ if attribute == attribute_data[:attribute]
252
+ return attribute_data[:foreign_klass]
253
+ end
254
+ end
255
+ end
256
+ end
257
+ end
174
258
  end
175
259
  end
@@ -1,3 +1,3 @@
1
1
  module RansackUI
2
- VERSION = "1.1.0"
2
+ VERSION = "1.2.0"
3
3
  end
data/lib/ransack_ui.rb CHANGED
@@ -1,6 +1,5 @@
1
1
  require "ransack_ui/version"
2
2
  require "ransack_ui/rails/engine"
3
- require "core_ext/enumerable"
4
3
  require "ransack_chronic"
5
4
 
6
5
  # Require ransack overrides
metadata CHANGED
@@ -1,20 +1,18 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ransack_ui
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
5
- prerelease:
4
+ version: 1.2.0
6
5
  platform: ruby
7
6
  authors:
8
7
  - Nathan Broadbent
9
8
  autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2013-01-21 00:00:00.000000000 Z
11
+ date: 2013-12-12 00:00:00.000000000 Z
13
12
  dependencies:
14
13
  - !ruby/object:Gem::Dependency
15
14
  name: ransack_chronic
16
15
  requirement: !ruby/object:Gem::Requirement
17
- none: false
18
16
  requirements:
19
17
  - - ! '>='
20
18
  - !ruby/object:Gem::Version
@@ -22,7 +20,6 @@ dependencies:
22
20
  type: :runtime
23
21
  prerelease: false
24
22
  version_requirements: !ruby/object:Gem::Requirement
25
- none: false
26
23
  requirements:
27
24
  - - ! '>='
28
25
  - !ruby/object:Gem::Version
@@ -30,7 +27,6 @@ dependencies:
30
27
  - !ruby/object:Gem::Dependency
31
28
  name: ransack
32
29
  requirement: !ruby/object:Gem::Requirement
33
- none: false
34
30
  requirements:
35
31
  - - ! '>='
36
32
  - !ruby/object:Gem::Version
@@ -38,7 +34,6 @@ dependencies:
38
34
  type: :runtime
39
35
  prerelease: false
40
36
  version_requirements: !ruby/object:Gem::Requirement
41
- none: false
42
37
  requirements:
43
38
  - - ! '>='
44
39
  - !ruby/object:Gem::Version
@@ -69,7 +64,6 @@ files:
69
64
  - app/views/ransack_ui/_search.html.erb
70
65
  - app/views/ransack_ui/_sort_fields.html.erb
71
66
  - config/locales/en.yml
72
- - lib/core_ext/enumerable.rb
73
67
  - lib/ransack_ui.rb
74
68
  - lib/ransack_ui/adapters/active_record.rb
75
69
  - lib/ransack_ui/adapters/active_record/base.rb
@@ -87,32 +81,25 @@ files:
87
81
  homepage: https://github.com/ndbroadbent/ransack_ui
88
82
  licenses:
89
83
  - MIT
84
+ metadata: {}
90
85
  post_install_message:
91
86
  rdoc_options: []
92
87
  require_paths:
93
88
  - lib
94
89
  required_ruby_version: !ruby/object:Gem::Requirement
95
- none: false
96
90
  requirements:
97
91
  - - ! '>='
98
92
  - !ruby/object:Gem::Version
99
93
  version: '0'
100
- segments:
101
- - 0
102
- hash: 3625052068884160403
103
94
  required_rubygems_version: !ruby/object:Gem::Requirement
104
- none: false
105
95
  requirements:
106
96
  - - ! '>='
107
97
  - !ruby/object:Gem::Version
108
98
  version: '0'
109
- segments:
110
- - 0
111
- hash: 3625052068884160403
112
99
  requirements: []
113
100
  rubyforge_project:
114
- rubygems_version: 1.8.24
101
+ rubygems_version: 2.1.11
115
102
  signing_key:
116
- specification_version: 3
103
+ specification_version: 4
117
104
  summary: UI Builder for Ransack
118
105
  test_files: []
@@ -1,3 +0,0 @@
1
- module Enumerable
2
- alias map_to each_with_object
3
- end