diff --git a/CHANGELOG.rdoc b/CHANGELOG.rdoc index baad3adeb..6cb948d10 100644 --- a/CHANGELOG.rdoc +++ b/CHANGELOG.rdoc @@ -1,4 +1,5 @@ - Rollback previous behaviour when submitting empty values, broken when default_value was added. Default value set in column is not used when trying to save empty value, DB default is used in that case, and save NULL when string is empty, as before. +- Support operators =, null and not null for :select search_ui, so it's possible to search for NULL with :select search UI. = 3.7.7 - Fix usage with mongoid, broken on 3.7.5 diff --git a/lib/active_scaffold/finder.rb b/lib/active_scaffold/finder.rb index 19ba582e7..aca5ca2c5 100644 --- a/lib/active_scaffold/finder.rb +++ b/lib/active_scaffold/finder.rb @@ -155,6 +155,7 @@ def subquery_condition(column, sql, options, values) end def condition_for_search_ui(column, value, like_pattern, search_ui) + Rails.logger.debug "param for #{column.name} #{value.inspect} #{value.is_a? Hash}" case search_ui when :boolean, :checkbox if value == 'null' @@ -164,13 +165,17 @@ def condition_for_search_ui(column, value, like_pattern, search_ui) end when :integer, :decimal, :float condition_for_numeric(column, value) - when :string, :range - condition_for_range(column, value, like_pattern) when :date, :time, :datetime, :timestamp condition_for_datetime(column, value) + when :string, :range + condition_for_range(column, value, like_pattern) when :select, :select_multiple, :draggable, :multi_select, :country, :usa_state, :chosen, :multi_chosen - values = Array(value).select(&:present?) - ['%s in (?)', values] if values.present? + if value.is_a?(Hash) + condition_for_range(column, value, like_pattern) + else + values = Array(value).select(&:present?) + ['%s in (?)', values] if values.present? + end else if column.text? value = column.active_record? ? column.active_record_class.sanitize_sql_like(value) : value @@ -178,7 +183,7 @@ def condition_for_search_ui(column, value, like_pattern, search_ui) else ['%s = ?', ActiveScaffold::Core.column_type_cast(value, column.column)] end - end + end.tap{|v|Rails.logger.debug "conditions for #{column.name}: #{v.inspect}"} end def condition_for_numeric(column, value) diff --git a/lib/active_scaffold/helpers/human_condition_helpers.rb b/lib/active_scaffold/helpers/human_condition_helpers.rb index 7c29b27f7..a0ab74ec0 100644 --- a/lib/active_scaffold/helpers/human_condition_helpers.rb +++ b/lib/active_scaffold/helpers/human_condition_helpers.rb @@ -104,6 +104,13 @@ def active_scaffold_human_condition_null(column, value) def active_scaffold_human_condition_select(column, associated) attribute = column.active_record_class.human_attribute_name(column.name) + if associated.is_a?(Hash) + if associated['opt'] == '=' + associated = associated['from'] + else + return active_scaffold_human_condition_range(column, associated) + end + end associated = [associated].compact unless associated.is_a? Array if column.association method = column.options[:label_method] || :to_label diff --git a/lib/active_scaffold/helpers/search_column_helpers.rb b/lib/active_scaffold/helpers/search_column_helpers.rb index 877a23940..1628b6d92 100644 --- a/lib/active_scaffold/helpers/search_column_helpers.rb +++ b/lib/active_scaffold/helpers/search_column_helpers.rb @@ -54,7 +54,7 @@ def active_scaffold_search_for(column, options = nil) # the standard active scaffold options used for class, name and scope def active_scaffold_search_options(column) - {:name => "search[#{column.name}]", :class => "#{column.name}-input", :id => "search_#{column.name}", :value => field_search_params[column.name.to_s]} + {name: "search[#{column.name}]", class: "#{column.name}-input", id: "search_#{column.name}", value: field_search_params[column.name.to_s]} end def search_attribute(column, record) @@ -98,6 +98,13 @@ def active_scaffold_search_multi_select(column, options, ui_options: column.opti def active_scaffold_search_select(column, html_options, options = {}, ui_options: column.options) record = html_options.delete(:object) associated = html_options.delete :value + if include_null_comparators?(column) + range_opts = html_options.slice(:name, :id) + range_opts[:opt_value], associated, _ = field_search_params_range_values(column) + operators = active_scaffold_search_select_comparator_options(column, ui_options: ui_options) + html_options[:name] += '[from]' + end + if column.association associated = associated.is_a?(Array) ? associated.map(&:to_i) : associated.to_i unless associated.nil? method = column.association.belongs_to? ? column.association.foreign_key : column.name @@ -119,13 +126,16 @@ def active_scaffold_search_select(column, html_options, options = {}, ui_options active_scaffold_translate_select_options(options) end - if (optgroup = options.delete(:optgroup)) - select(:record, method, active_scaffold_grouped_options(column, select_options, optgroup), options, html_options) - elsif column.association - collection_select(:record, method, select_options, :id, ui_options[:label_method] || :to_label, options, html_options) - else - select(:record, method, select_options, options, html_options) - end + select = + if (optgroup = options.delete(:optgroup)) + select(:record, method, active_scaffold_grouped_options(column, select_options, optgroup), options, html_options) + elsif column.association + collection_select(:record, method, select_options, :id, ui_options[:label_method] || :to_label, options, html_options) + else + select(:record, method, select_options, options, html_options) + end + + operators ? build_active_scaffold_search_range_ui(operators, select, **range_opts) : select end def active_scaffold_search_select_multiple(column, options, ui_options: column.options) @@ -207,6 +217,12 @@ def active_scaffold_search_range_comparator_options(column, ui_options: column.o select_options end + def active_scaffold_search_select_comparator_options(column, ui_options: column.options) + select_options = [[as_('='.to_sym), '=']] + select_options.concat(ActiveScaffold::Finder::NULL_COMPARATORS.collect { |comp| [as_(comp), comp] }) + select_options + end + def include_null_comparators?(column, ui_options: column.options) return ui_options[:null_comparators] if ui_options.key? :null_comparators if column.association @@ -219,30 +235,37 @@ def include_null_comparators?(column, ui_options: column.options) def active_scaffold_search_range(column, options, input_method = :text_field_tag, input_options = {}, ui_options: column.options) opt_value, from_value, to_value = field_search_params_range_values(column) - select_options = active_scaffold_search_range_comparator_options(column, ui_options: ui_options) + operators = active_scaffold_search_range_comparator_options(column, ui_options: ui_options) text_field_size = active_scaffold_search_range_string?(column) ? 15 : 10 - opt_value ||= select_options[0][1] from_value = controller.class.condition_value_for_numeric(column, from_value) to_value = controller.class.condition_value_for_numeric(column, to_value) from_value = format_number_value(from_value, ui_options) if from_value.is_a?(Numeric) to_value = format_number_value(to_value, ui_options) if to_value.is_a?(Numeric) - html = select_tag("#{options[:name]}[opt]", options_for_select(select_options, opt_value), - :id => "#{options[:id]}_opt", :class => 'as_search_range_option') from_options = active_scaffold_input_text_options(input_options.merge(:id => options[:id], :size => text_field_size)) to_options = from_options.merge(:id => "#{options[:id]}_to") - html << content_tag('span', :id => "#{options[:id]}_numeric", :style => ActiveScaffold::Finder::NULL_COMPARATORS.include?(opt_value) ? 'display: none' : nil) do - send(input_method, "#{options[:name]}[from]", from_value, input_options) << - content_tag( - :span, - safe_join([' - ', send(input_method, "#{options[:name]}[to]", to_value, to_options)]), - :id => "#{options[:id]}_between", :class => 'as_search_range_between', :style => ('display: none' unless opt_value == 'BETWEEN') - ) - end - content_tag :span, html, :class => 'search_range' + + from_field = send(input_method, "#{options[:name]}[from]", from_value, input_options) + to_field = send(input_method, "#{options[:name]}[to]", to_value, to_options) + build_active_scaffold_search_range_ui(operators, from_field, to_field, opt_value: opt_value, **options.slice(:name, :id)) end alias active_scaffold_search_string active_scaffold_search_range + def build_active_scaffold_search_range_ui(operators, from, to = nil, name:, id:, opt_value: nil) + opt_value ||= operators[0][1] + html = select_tag("#{name}[opt]", options_for_select(operators, opt_value), + id: "#{id}_opt", class: 'as_search_range_option') + if to + from << content_tag( + :span, + safe_join([' - ', to]), + id: "#{id}_between", class: 'as_search_range_between', style: ('display: none' unless opt_value == 'BETWEEN') + ) + end + html << content_tag('span', from, id: "#{id}_numeric", style: ActiveScaffold::Finder::NULL_COMPARATORS.include?(opt_value) ? 'display: none' : nil) + content_tag :span, html, class: 'search_range' + end + def active_scaffold_search_integer(column, options, ui_options: column.options) number_opts = ui_options.slice(:step, :min, :max).reverse_merge(step: '1') active_scaffold_search_range(column, options, :number_field_tag, number_opts, ui_options: ui_options)