1
0
Fork 0
mirror of https://github.com/activerecord-hackery/ransack.git synced 2022-11-09 13:47:45 -05:00
activerecord-hackery--ransack/lib/ransack/helpers/form_helper.rb

209 lines
6.2 KiB
Ruby
Raw Normal View History

2011-03-30 20:31:39 -04:00
module Ransack
module Helpers
module FormHelper
# +search_form_for+
#
2014-12-08 18:11:21 -05:00
# <%= search_form_for(@q) do |f| %>
#
2011-03-30 20:31:39 -04:00
def search_form_for(record, options = {}, &proc)
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)
)
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(
record.map { |o| o.is_a? Ransack::Search ? o.klass : o },
2014-04-18 04:49:38 -04:00
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,
'No Ransack::Search object was provided to search_form_for!'
2011-03-30 20:31:39 -04:00
end
options[:html] ||= {}
html_options = {
:class => options[:class].present? ?
2013-12-10 13:18:17 -05:00
"#{options[:class]}" :
"#{search.klass.to_s.underscore}_search",
:id => options[:id].present? ?
2013-12-10 13:18:17 -05:00
"#{options[:id]}" :
"#{search.klass.to_s.underscore}_search",
:method => :get
2011-03-30 20:31:39 -04: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
# +sort_link+
#
2014-12-08 18:11:21 -05:00
# <%= sort_link(@q, :name, [:name, 'kind ASC'], 'Player Name') %>
#
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
s = SortLinkParser.new(search, attribute, args, params)
link_to(
s.name, url(routing_proxy, s.options_for_url), s.html_options(args)
)
end
private
def extract_search_and_routing_proxy(search)
if search.is_a? Array
[search.second, search.first]
else
[search, nil]
end
2014-12-08 18:11:21 -05:00
end
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
end
class SortLinkParser
def initialize(search, attribute, args, params)
@search = search
@params = params
field = attribute.to_s
sort_fields = extract_sort_fields_and_mutate_args!(field, args).compact
@current_dir = existing_sort_direction(field)
@label_text = extract_label_and_mutate_args!(field, args)
@options = extract_options_and_mutate_args!(args)
@hide_indicator = @options.delete :hide_indicator
@default_order = @options.delete :default_order
@sort_params = build_sort(sort_fields)
@sort_params = @sort_params.first if @sort_params.size == 1
end
def name
[ERB::Util.h(@label_text), order_indicator]
.compact
.join(Constants::NON_BREAKING_SPACE)
.html_safe
end
def options_for_url
@params.merge(
@options.merge(
@search.context.search_key => search_and_sort_params))
end
def html_options(args)
html_options = extract_options_and_mutate_args!(args)
html_options.merge(class:
[[Constants::SORT_LINK, @current_dir], html_options[:class]]
.compact.join(Constants::SPACE)
)
end
private
def extract_sort_fields_and_mutate_args!(field, args)
if args.first.is_a? Array
args.shift
else
[field]
end
end
def extract_label_and_mutate_args!(field, args)
if args.first.is_a? String
args.shift
else
Translate.attribute(field, :context => @search.context)
end
end
def extract_options_and_mutate_args!(args)
if args.first.is_a? Hash
args.shift.with_indifferent_access
else
{}
end
end
def search_and_sort_params
search_params.merge(:s => @sort_params)
end
def search_params
@params[@search.context.search_key].presence || {}
end
def build_sort(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?
[parse_sort(fields[0])] + build_sort(fields.drop(1))
end
def parse_sort(field)
Use Array#+ instead of in-place mutating with Array#<< and clarify the code by extracting the parsing to a separate method. Array#+ creates a new array rather than mutating the provided one and ought to in theory be safer and slower. However, given the small number of sort array elements (usually 1, up to 3 or 4), there is very little performance difference — about the same as the benchmark variances. Benchmark.ips do |x| array = ['updated_at desc', 'first_name asc'] def parse_sort(field) attr_name, new_dir = field.to_s.split(/\s+/) "#{attr_name} #{new_dir}" end def inject(array) array.inject([]) { |a, field| a + [parse_sort(field)] } end def inject_mutate_in_place(array) array.inject([]) { |a, field| a << parse_sort(field) } end def each_with_object_mutate_in_place(array) array.each_with_object([]) { |field, a| a << parse_sort(field) } end x.report('inject +') do |times| i = 0 while i < times inject(array) i += 1 end end x.report('inject <<') do |times| i = 0 while i < times inject_mutate_in_place(array) i += 1 end end x.report('each_with_object <<') do |times| i = 0 while i < times inject_mutate_in_place(array) i += 1 end end x.compare! end end Calculating ------------------------------------- inject + 9.145k i/100ms inject << 9.302k i/100ms each_with_object << 10.478k i/100ms ------------------------------------------------- inject + 179.340k (± 5.3%) i/s - 896.210k inject << 192.993k (± 5.9%) i/s - 967.408k each_with_object << 192.782k (± 5.5%) i/s - 963.976k Comparison: inject <<: 192993.0 i/s each_with_object <<: 192782.2 i/s - 1.00x slower inject +: 179339.7 i/s - 1.08x slower
2014-12-14 08:01:58 -05:00
attr_name, new_dir = field.to_s.split(/\s+/)
if no_sort_direction_specified?(new_dir)
new_dir = detect_previous_sort_direction_and_invert_it(attr_name)
Use Array#+ instead of in-place mutating with Array#<< and clarify the code by extracting the parsing to a separate method. Array#+ creates a new array rather than mutating the provided one and ought to in theory be safer and slower. However, given the small number of sort array elements (usually 1, up to 3 or 4), there is very little performance difference — about the same as the benchmark variances. Benchmark.ips do |x| array = ['updated_at desc', 'first_name asc'] def parse_sort(field) attr_name, new_dir = field.to_s.split(/\s+/) "#{attr_name} #{new_dir}" end def inject(array) array.inject([]) { |a, field| a + [parse_sort(field)] } end def inject_mutate_in_place(array) array.inject([]) { |a, field| a << parse_sort(field) } end def each_with_object_mutate_in_place(array) array.each_with_object([]) { |field, a| a << parse_sort(field) } end x.report('inject +') do |times| i = 0 while i < times inject(array) i += 1 end end x.report('inject <<') do |times| i = 0 while i < times inject_mutate_in_place(array) i += 1 end end x.report('each_with_object <<') do |times| i = 0 while i < times inject_mutate_in_place(array) i += 1 end end x.compare! end end Calculating ------------------------------------- inject + 9.145k i/100ms inject << 9.302k i/100ms each_with_object << 10.478k i/100ms ------------------------------------------------- inject + 179.340k (± 5.3%) i/s - 896.210k inject << 192.993k (± 5.9%) i/s - 967.408k each_with_object << 192.782k (± 5.5%) i/s - 963.976k Comparison: inject <<: 192993.0 i/s each_with_object <<: 192782.2 i/s - 1.00x slower inject +: 179339.7 i/s - 1.08x slower
2014-12-14 08:01:58 -05:00
end
"#{attr_name} #{new_dir}"
end
def detect_previous_sort_direction_and_invert_it(attr_name)
sort_dir = existing_sort_direction(attr_name)
if sort_dir
direction_text(sort_dir)
else
default_sort_order(attr_name) || Constants::ASC
end
end
def existing_sort_direction(attr_name)
if sort = @search.sorts.detect { |s| s && s.name == attr_name }
sort.dir
end
end
def default_sort_order(attr_name)
Hash === @default_order ? @default_order[attr_name] : @default_order
end
def order_indicator
if @hide_indicator || no_sort_direction_specified?
nil
else
direction_arrow
end
end
def no_sort_direction_specified?(dir = @current_dir)
!Constants::ASC_DESC.include?(dir)
end
def direction_arrow
if @current_dir == Constants::DESC
Constants::DESC_ARROW
else
Constants::ASC_ARROW
end
end
def direction_text(dir)
if dir == Constants::DESC
Constants::ASC
else
Constants::DESC
end
end
2014-12-08 18:11:21 -05:00
end
2011-03-30 20:31:39 -04:00
end
end
end