2011-03-30 20:31:39 -04:00
|
|
|
module Ransack
|
|
|
|
module Helpers
|
|
|
|
module FormHelper
|
2011-08-13 16:36:40 -04:00
|
|
|
|
2014-12-07 14:38:51 -05:00
|
|
|
# +search_form_for+
|
|
|
|
#
|
2014-12-08 18:11:21 -05:00
|
|
|
# <%= search_form_for(@q) do |f| %>
|
2014-12-07 14:38:51 -05:00
|
|
|
#
|
2011-03-30 20:31:39 -04:00
|
|
|
def search_form_for(record, options = {}, &proc)
|
2014-12-07 14:38:51 -05:00
|
|
|
if record.is_a? Ransack::Search
|
2011-03-30 20:31:39 -04:00
|
|
|
search = record
|
2014-04-18 04:49:38 -04:00
|
|
|
options[:url] ||= polymorphic_path(
|
|
|
|
search.klass, format: options.delete(:format)
|
|
|
|
)
|
2015-01-14 11:29:13 -05:00
|
|
|
elsif record.is_a?(Array) &&
|
|
|
|
(search = record.detect { |o| o.is_a?(Ransack::Search) })
|
2013-08-04 09:13:41 -04:00
|
|
|
options[:url] ||= polymorphic_path(
|
2015-07-26 09:02:27 -04:00
|
|
|
options_for(record), format: options.delete(:format)
|
2013-12-10 13:18:17 -05:00
|
|
|
)
|
2011-03-30 20:31:39 -04:00
|
|
|
else
|
2013-08-04 09:13:41 -04:00
|
|
|
raise ArgumentError,
|
2015-01-14 11:29:13 -05:00
|
|
|
'No Ransack::Search object was provided to search_form_for!'
|
2011-03-30 20:31:39 -04:00
|
|
|
end
|
|
|
|
options[:html] ||= {}
|
|
|
|
html_options = {
|
2015-07-26 09:02:27 -04:00
|
|
|
class: html_option_for(options[:class], search),
|
|
|
|
id: html_option_for(options[:id], search),
|
|
|
|
method: :get
|
2011-03-30 20:31:39 -04:00
|
|
|
}
|
2014-11-15 14:56:19 -05:00
|
|
|
options[:as] ||= Ransack.options[:search_key]
|
2011-03-30 20:31:39 -04:00
|
|
|
options[:html].reverse_merge!(html_options)
|
|
|
|
options[:builder] ||= FormBuilder
|
|
|
|
|
|
|
|
form_for(record, options, &proc)
|
|
|
|
end
|
|
|
|
|
2014-12-07 14:38:51 -05:00
|
|
|
# +sort_link+
|
|
|
|
#
|
2014-12-08 18:11:21 -05:00
|
|
|
# <%= sort_link(@q, :name, [:name, 'kind ASC'], 'Player Name') %>
|
2014-12-07 14:38:51 -05:00
|
|
|
#
|
2014-12-11 15:26:25 -05:00
|
|
|
def sort_link(search_object, attribute, *args)
|
|
|
|
search, routing_proxy = extract_search_and_routing_proxy(search_object)
|
|
|
|
unless Search === search
|
|
|
|
raise TypeError, 'First argument must be a Ransack::Search!'
|
|
|
|
end
|
2014-12-19 07:35:40 -05:00
|
|
|
s = SortLink.new(search, attribute, args, params)
|
|
|
|
link_to(s.name, url(routing_proxy, s.url_options), s.html_options(args))
|
2014-12-07 14:38:51 -05:00
|
|
|
end
|
2012-03-28 15:00:19 -04:00
|
|
|
|
2014-12-07 14:38:51 -05:00
|
|
|
private
|
2011-08-13 16:36:40 -04:00
|
|
|
|
2015-07-26 09:02:27 -04:00
|
|
|
def options_for(record)
|
2015-08-29 07:02:41 -04:00
|
|
|
record.map { |r| parse_record(r) }
|
2015-07-26 09:02:27 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def parse_record(object)
|
2015-08-29 05:57:19 -04:00
|
|
|
return object.klass if object.is_a?(Ransack::Search)
|
|
|
|
object
|
2015-07-26 09:02:27 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def html_option_for(option, search)
|
2015-08-29 05:57:19 -04:00
|
|
|
return option.to_s if option.present?
|
|
|
|
"#{search.klass.to_s.underscore}_search"
|
2015-07-26 09:02:27 -04:00
|
|
|
end
|
|
|
|
|
2014-12-11 15:26:25 -05:00
|
|
|
def extract_search_and_routing_proxy(search)
|
2015-08-29 06:19:18 -04:00
|
|
|
return [search[1], search[0]] if search.is_a?(Array)
|
2015-08-29 05:57:19 -04:00
|
|
|
[search, nil]
|
2014-12-08 18:11:21 -05:00
|
|
|
end
|
2014-12-07 21:03:00 -05:00
|
|
|
|
2014-12-11 15:26:25 -05:00
|
|
|
def url(routing_proxy, options_for_url)
|
|
|
|
if routing_proxy && respond_to?(routing_proxy)
|
|
|
|
send(routing_proxy).url_for(options_for_url)
|
|
|
|
else
|
|
|
|
url_for(options_for_url)
|
|
|
|
end
|
2014-12-07 21:03:00 -05:00
|
|
|
end
|
|
|
|
|
2014-12-19 07:35:40 -05:00
|
|
|
class SortLink
|
2014-12-14 15:16:17 -05:00
|
|
|
def initialize(search, attribute, args, params)
|
|
|
|
@search = search
|
|
|
|
@params = params
|
2014-12-19 07:35:40 -05:00
|
|
|
@field = attribute.to_s
|
2015-07-26 08:43:18 -04:00
|
|
|
@sort_fields = extract_sort_fields_and_mutate_args!(args).compact
|
2014-12-19 07:35:40 -05:00
|
|
|
@current_dir = existing_sort_direction
|
|
|
|
@label_text = extract_label_and_mutate_args!(args)
|
2014-12-11 15:26:25 -05:00
|
|
|
@options = extract_options_and_mutate_args!(args)
|
2015-08-21 13:21:32 -04:00
|
|
|
@hide_indicator = @options.delete(:hide_indicator) ||
|
|
|
|
Ransack.options[:hide_sort_order_indicators]
|
2014-12-11 15:26:25 -05:00
|
|
|
@default_order = @options.delete :default_order
|
2014-12-07 21:03:00 -05:00
|
|
|
end
|
|
|
|
|
2014-12-11 15:26:25 -05:00
|
|
|
def name
|
|
|
|
[ERB::Util.h(@label_text), order_indicator]
|
2014-12-14 15:16:17 -05:00
|
|
|
.compact
|
|
|
|
.join(Constants::NON_BREAKING_SPACE)
|
|
|
|
.html_safe
|
2014-12-07 21:03:00 -05:00
|
|
|
end
|
|
|
|
|
2014-12-19 07:35:40 -05:00
|
|
|
def url_options
|
2014-12-14 15:16:17 -05:00
|
|
|
@params.merge(
|
2014-12-11 15:26:25 -05:00
|
|
|
@options.merge(
|
2014-12-14 15:16:17 -05:00
|
|
|
@search.context.search_key => search_and_sort_params))
|
2014-12-07 14:38:51 -05:00
|
|
|
end
|
Add multiple sort field support in sort_link
This patch allows users to sort on multiple fields with the sort_link
helper.
To specify sorting on multiple fields:
sort_link(:kind, [:kind, 'name asc'])
This will create a sort link that sorts first by kind, and then by
name. The first `:kind` parameter ensures that the link generated
shows the sort status of the `kind` field.
When you specify a sort direction in the sort fields array, the
direction is locked to that direction. In the above example, clicking
the resulting link would toggle sorting of the kind field, but the name
field would always sort ascending.
Also added was the ability to specify multiple default_order fields
with a hash:
sort_link(:kind, [:kind, :name],
:default_order => { :name => 'asc', :kind => 'desc' })
Clicking the resulting link will toggle the sort directions of both
`name` and `kind`, sorting the `name` field by default ascending, and
the `kind` field descending.
2014-09-30 18:58:33 -04:00
|
|
|
|
2014-12-11 15:26:25 -05:00
|
|
|
def html_options(args)
|
|
|
|
html_options = extract_options_and_mutate_args!(args)
|
2015-07-26 08:43:18 -04:00
|
|
|
html_options.merge(
|
|
|
|
class: [[Constants::SORT_LINK, @current_dir], html_options[:class]]
|
|
|
|
.compact.join(Constants::SPACE)
|
|
|
|
)
|
2014-12-07 14:38:51 -05:00
|
|
|
end
|
Add multiple sort field support in sort_link
This patch allows users to sort on multiple fields with the sort_link
helper.
To specify sorting on multiple fields:
sort_link(:kind, [:kind, 'name asc'])
This will create a sort link that sorts first by kind, and then by
name. The first `:kind` parameter ensures that the link generated
shows the sort status of the `kind` field.
When you specify a sort direction in the sort fields array, the
direction is locked to that direction. In the above example, clicking
the resulting link would toggle sorting of the kind field, but the name
field would always sort ascending.
Also added was the ability to specify multiple default_order fields
with a hash:
sort_link(:kind, [:kind, :name],
:default_order => { :name => 'asc', :kind => 'desc' })
Clicking the resulting link will toggle the sort directions of both
`name` and `kind`, sorting the `name` field by default ascending, and
the `kind` field descending.
2014-09-30 18:58:33 -04:00
|
|
|
|
2014-12-11 15:26:25 -05:00
|
|
|
private
|
2014-10-03 14:54:20 -04:00
|
|
|
|
2014-12-19 07:35:40 -05:00
|
|
|
def extract_sort_fields_and_mutate_args!(args)
|
2015-08-29 06:19:18 -04:00
|
|
|
return args.shift if args[0].is_a?(Array)
|
2015-08-29 05:57:19 -04:00
|
|
|
[@field]
|
2014-12-11 15:26:25 -05:00
|
|
|
end
|
Add multiple sort field support in sort_link
This patch allows users to sort on multiple fields with the sort_link
helper.
To specify sorting on multiple fields:
sort_link(:kind, [:kind, 'name asc'])
This will create a sort link that sorts first by kind, and then by
name. The first `:kind` parameter ensures that the link generated
shows the sort status of the `kind` field.
When you specify a sort direction in the sort fields array, the
direction is locked to that direction. In the above example, clicking
the resulting link would toggle sorting of the kind field, but the name
field would always sort ascending.
Also added was the ability to specify multiple default_order fields
with a hash:
sort_link(:kind, [:kind, :name],
:default_order => { :name => 'asc', :kind => 'desc' })
Clicking the resulting link will toggle the sort directions of both
`name` and `kind`, sorting the `name` field by default ascending, and
the `kind` field descending.
2014-09-30 18:58:33 -04:00
|
|
|
|
2014-12-19 07:35:40 -05:00
|
|
|
def extract_label_and_mutate_args!(args)
|
2015-08-29 06:19:18 -04:00
|
|
|
return args.shift if args[0].is_a?(String)
|
2015-08-29 05:57:19 -04:00
|
|
|
Translate.attribute(@field, context: @search.context)
|
2014-12-11 15:26:25 -05:00
|
|
|
end
|
2011-08-13 16:36:40 -04:00
|
|
|
|
2014-12-11 15:26:25 -05:00
|
|
|
def extract_options_and_mutate_args!(args)
|
2015-08-29 06:19:18 -04:00
|
|
|
return args.shift.with_indifferent_access if args[0].is_a?(Hash)
|
2015-08-29 05:57:19 -04:00
|
|
|
{}
|
2014-12-11 15:26:25 -05:00
|
|
|
end
|
2012-03-07 14:31:13 -05:00
|
|
|
|
2014-12-14 15:16:17 -05:00
|
|
|
def search_and_sort_params
|
2015-07-26 08:43:18 -04:00
|
|
|
search_params.merge(s: sort_params)
|
2014-12-06 17:58:03 -05:00
|
|
|
end
|
|
|
|
|
2014-12-14 15:16:17 -05:00
|
|
|
def search_params
|
2014-12-14 16:48:39 -05:00
|
|
|
@params[@search.context.search_key].presence || {}
|
2014-12-11 15:26:25 -05:00
|
|
|
end
|
2014-10-03 14:54:20 -04:00
|
|
|
|
2015-07-26 08:43:18 -04:00
|
|
|
def sort_params
|
|
|
|
sort_array = recursive_sort_params_build(@sort_fields)
|
2015-08-29 06:19:18 -04:00
|
|
|
return sort_array[0] if sort_array.length == 1
|
2015-08-29 05:57:19 -04:00
|
|
|
sort_array
|
2015-07-26 08:43:18 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def recursive_sort_params_build(fields)
|
Yay for recursion :)
Recursion is usually inefficient in Ruby, but in this case with a small
number of elements it is actually faster. Nothing earth-shaking, but it
is fun to see recursion come out ahead once in a while in Ruby-land.
Yay!
Benchmark.ips do |x|
array = ['updated_at desc', 'first_name asc', 'date desc']
def parse_sort(field)
attr_name, new_dir = field.to_s.split(/\s+/)
"#{attr_name} #{new_dir}"
end
def recursive_add(array)
return [] if array.empty?
[parse_sort(array[0])] + recursive_add(array.drop 1)
end
def inject_add(array)
array.inject([]) { |a, field| a + [parse_sort(field)] }
end
def inject_append(array)
array.inject([]) { |a, field| a << parse_sort(field) }
end
def each_with_object_append(array)
array.each_with_object([]) { |field, a| a << parse_sort(field) }
end
x.report('recursive +') do |times|
i = 0
while i < times
recursive_add(array)
i += 1
end
end
x.report('inject +') do |times|
i = 0
while i < times
inject_add(array)
i += 1
end
end
x.report('inject <<') do |times|
i = 0
while i < times
inject_append(array)
i += 1
end
end
x.report('each_with_object <<') do |times|
i = 0
while i < times
each_with_object_append(array)
i += 1
end
end
x.compare!
end
Base case 1 element (array = ['updated_at desc’]):
∴ rake benchmarks:inject_versus_recursive
Calculating -------------------------------------
recursive + 12.732k i/100ms
inject + 11.834k i/100ms
inject << 12.096k i/100ms
each_with_object << 12.535k i/100ms
-------------------------------------------------
recursive + 361.342k (±11.6%) i/s - 1.782M
inject + 275.680k (±10.8%) i/s - 1.373M
inject << 330.712k (± 9.1%) i/s - 1.645M
each_with_object << 345.253k (± 8.1%) i/s - 1.717M
Comparison:
recursive +: 361341.9 i/s
each_with_object <<: 345253.4 i/s - 1.05x slower
inject <<: 330712.3 i/s - 1.09x slower
inject +: 275679.5 i/s - 1.31x slower
With 3 elements (array = ['updated_at desc', 'first_name asc', 'date
desc']):
∴ rake benchmarks:inject_versus_recursive
Calculating -------------------------------------
recursive + 8.590k i/100ms
inject + 8.292k i/100ms
inject << 8.776k i/100ms
each_with_object << 8.665k i/100ms
-------------------------------------------------
recursive + 151.233k (± 2.8%) i/s - 755.920k
inject + 135.586k (± 4.3%) i/s - 679.944k
inject << 144.773k (± 5.8%) i/s - 728.408k
each_with_object << 147.728k (± 5.6%) i/s - 736.525k
Comparison:
recursive +: 151233.3 i/s
each_with_object <<: 147727.8 i/s - 1.02x slower
inject <<: 144772.9 i/s - 1.04x slower
inject +: 135586.4 i/s - 1.12x slower
2014-12-14 08:49:00 -05:00
|
|
|
return [] if fields.empty?
|
2015-07-26 08:43:18 -04:00
|
|
|
[parse_sort(fields[0])] + recursive_sort_params_build(fields.drop 1)
|
2014-12-11 15:26:25 -05:00
|
|
|
end
|
2014-12-07 14:38:51 -05:00
|
|
|
|
2014-12-14 15:16:17 -05:00
|
|
|
def parse_sort(field)
|
2014-12-14 08:01:58 -05:00
|
|
|
attr_name, new_dir = field.to_s.split(/\s+/)
|
|
|
|
if no_sort_direction_specified?(new_dir)
|
2014-12-14 15:16:17 -05:00
|
|
|
new_dir = detect_previous_sort_direction_and_invert_it(attr_name)
|
2014-12-14 08:01:58 -05:00
|
|
|
end
|
|
|
|
"#{attr_name} #{new_dir}"
|
|
|
|
end
|
|
|
|
|
2014-12-14 15:16:17 -05:00
|
|
|
def detect_previous_sort_direction_and_invert_it(attr_name)
|
2015-07-26 08:43:18 -04:00
|
|
|
if sort_dir = existing_sort_direction(attr_name)
|
2014-12-11 15:26:25 -05:00
|
|
|
direction_text(sort_dir)
|
|
|
|
else
|
|
|
|
default_sort_order(attr_name) || Constants::ASC
|
|
|
|
end
|
|
|
|
end
|
2014-12-06 17:58:03 -05:00
|
|
|
|
2015-08-29 05:57:19 -04:00
|
|
|
def existing_sort_direction(f = @field)
|
|
|
|
return unless sort = @search.sorts.detect { |s| s && s.name == f }
|
|
|
|
sort.dir
|
2014-12-11 15:26:25 -05:00
|
|
|
end
|
2014-12-06 17:58:03 -05:00
|
|
|
|
2014-12-11 15:26:25 -05:00
|
|
|
def default_sort_order(attr_name)
|
2015-08-29 05:57:19 -04:00
|
|
|
return @default_order[attr_name] if Hash === @default_order
|
|
|
|
@default_order
|
2014-12-11 15:26:25 -05:00
|
|
|
end
|
2014-12-06 17:58:03 -05:00
|
|
|
|
2014-12-11 15:26:25 -05:00
|
|
|
def order_indicator
|
2015-08-27 18:07:02 -04:00
|
|
|
return if @hide_indicator || no_sort_direction_specified?
|
|
|
|
direction_arrow
|
2014-12-11 15:26:25 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
def no_sort_direction_specified?(dir = @current_dir)
|
|
|
|
!Constants::ASC_DESC.include?(dir)
|
|
|
|
end
|
|
|
|
|
|
|
|
def direction_arrow
|
2015-08-29 05:57:19 -04:00
|
|
|
return Constants::DESC_ARROW if @current_dir == Constants::DESC
|
|
|
|
Constants::ASC_ARROW
|
2014-12-11 15:26:25 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
def direction_text(dir)
|
2015-08-29 05:57:19 -04:00
|
|
|
return Constants::ASC if dir == Constants::DESC
|
|
|
|
Constants::DESC
|
2014-12-11 15:26:25 -05:00
|
|
|
end
|
2014-12-08 18:11:21 -05:00
|
|
|
end
|
2011-03-30 20:31:39 -04:00
|
|
|
end
|
|
|
|
end
|
2012-04-11 11:58:27 -04:00
|
|
|
end
|