diff --git a/lib/ransack/predicate.rb b/lib/ransack/predicate.rb index 64941811..8f9fa13d 100644 --- a/lib/ransack/predicate.rb +++ b/lib/ransack/predicate.rb @@ -38,7 +38,7 @@ def initialize(opts = {}) @type = opts[:type] @formatter = opts[:formatter] @validator = opts[:validator] || - lambda { |v| v.respond_to?(:empty?) ? !v.empty? : !v.nil? } + lambda { |v| v.is_a?(String) || (v.respond_to?(:empty?) ? !v.empty? : !v.nil?) } @compound = opts[:compound] @wants_array = opts.fetch(:wants_array, @compound || Constants::IN_NOT_IN.include?(@arel_predicate)) diff --git a/lib/ransack/search.rb b/lib/ransack/search.rb index 03115c5a..17a68976 100644 --- a/lib/ransack/search.rb +++ b/lib/ransack/search.rb @@ -26,7 +26,12 @@ def initialize(object, params = {}, options = {}) if params.is_a? Hash params = params.dup params = params.transform_values { |v| v.is_a?(String) && strip_whitespace ? v.strip : v } - params.delete_if { |k, v| [*v].all?{ |i| i.blank? && i != false } } + params.delete_if { |k, v| + [*v].all? do |i| + i.nil? || + (i.respond_to?(:blank?) && i.blank? && !i.is_a?(String) && i != false) + end + } else params = {} end diff --git a/spec/ransack/search_spec.rb b/spec/ransack/search_spec.rb index 850bbe6b..d356d4e0 100644 --- a/spec/ransack/search_spec.rb +++ b/spec/ransack/search_spec.rb @@ -4,7 +4,7 @@ module Ransack describe Search do describe '#initialize' do it 'removes empty conditions before building' do - expect_any_instance_of(Search).to receive(:build).with({}) + expect_any_instance_of(Search).to receive(:build).with({ 'name_eq' => '' }) Search.new(Person, name_eq: '') end @@ -58,8 +58,8 @@ module Ransack end end - it 'removes empty suffixed conditions before building' do - expect_any_instance_of(Search).to receive(:build).with({}) + it 'keeps empty suffixed conditions before building' do + expect_any_instance_of(Search).to receive(:build).with({ 'name_eq_any' => [''] }) Search.new(Person, name_eq_any: ['']) end @@ -184,12 +184,60 @@ module Ransack expect(s.result.to_sql).to include 'published' end - it 'discards empty conditions' do + it 'does not discard conditions with empty string values' do s = Search.new(Person, children_name_eq: '') condition = s.base[:children_name_eq] + expect(condition).not_to be_nil + expect(condition.values.first.value).to eq('') + end + + it 'discards nil conditions' do + s = Search.new(Person, children_name_eq: nil) + condition = s.base[:children_name_eq] expect(condition).to be_nil end + it 'should not discard conditions with empty string values in arrays' do + s = Search.new(Person, name_in: ['', 'John']) + condition = s.base[:name_in] + expect(condition).not_to be_nil + expect(condition.values.map(&:value)).to eq(['', 'John']) + end + + it 'should not discard eq conditions with empty string values' do + s = Search.new(Person, name_eq: '') + condition = s.base[:name_eq] + expect(condition).not_to be_nil + expect(condition.values.first.value).to eq('') + end + + context 'issue: empty string values in predicates' do + it 'includes empty strings in IN predicates alongside other values' do + s = Search.new(Person, name_in: ['', 'John', 'Jane']) + condition = s.base[:name_in] + expect(condition).not_to be_nil + expect(condition.values.map(&:value)).to eq(['', 'John', 'Jane']) + end + + it 'creates condition for eq predicate with empty string' do + s = Search.new(Person, name_eq: '') + condition = s.base[:name_eq] + expect(condition).not_to be_nil + expect(condition.values.first.value).to eq('') + end + + it 'handles mixed predicates with empty strings and regular values' do + s = Search.new(Person, name_eq: '', email_eq: 'test@example.com') + name_condition = s.base[:name_eq] + email_condition = s.base[:email_eq] + + expect(name_condition).not_to be_nil + expect(name_condition.values.first.value).to eq('') + expect(email_condition).not_to be_nil + expect(email_condition.values.first.value).to eq('test@example.com') + end + end + it 'accepts base grouping condition as an option' do expect(Nodes::Grouping).to receive(:new).with(kind_of(Context), 'or') Search.new(Person, {}, { grouping: 'or' })