Skip to content

Commit

Permalink
support operators =, null and not null for :select search_ui, so it's…
Browse files Browse the repository at this point in the history
… possible to search for NULL with :select search UI.
  • Loading branch information
scambra committed Oct 10, 2024
1 parent 33bf8e6 commit 806a181
Show file tree
Hide file tree
Showing 4 changed files with 62 additions and 26 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.rdoc
Original file line number Diff line number Diff line change
@@ -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
Expand Down
15 changes: 10 additions & 5 deletions lib/active_scaffold/finder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -164,21 +165,25 @@ 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?)
['%<search_sql>s in (?)', values] if values.present?
if value.is_a?(Hash)
condition_for_range(column, value, like_pattern)
else
values = Array(value).select(&:present?)
['%<search_sql>s in (?)', values] if values.present?
end
else
if column.text?
value = column.active_record? ? column.active_record_class.sanitize_sql_like(value) : value
["%<search_sql>s #{ActiveScaffold::Finder.like_operator} ?", like_pattern.sub('?', value)]
else
['%<search_sql>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)
Expand Down
7 changes: 7 additions & 0 deletions lib/active_scaffold/helpers/human_condition_helpers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
65 changes: 44 additions & 21 deletions lib/active_scaffold/helpers/search_column_helpers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -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
Expand All @@ -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)
Expand Down

0 comments on commit 806a181

Please sign in to comment.