From fa5bf7c0b96be87efa2914841d73553aa1e47238 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 24 Sep 2025 13:58:56 +0000 Subject: [PATCH 1/3] Initial plan From fe3337780ce067b6dd74dc738a16d41f7dd938ec Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 24 Sep 2025 14:06:38 +0000 Subject: [PATCH 2/3] Fix empty string predicate filtering - allow empty strings in conditions Co-authored-by: scarroll32 <11340230+scarroll32@users.noreply.github.com> --- lib/ransack/predicate.rb | 2 +- lib/ransack/search.rb | 7 ++++++- spec/ransack/search_spec.rb | 25 +++++++++++++++++++++++-- 3 files changed, 30 insertions(+), 4 deletions(-) diff --git a/lib/ransack/predicate.rb b/lib/ransack/predicate.rb index 649418116..8f9fa13d2 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 03115c5ac..17a689769 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 26433199d..7474043de 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 @@ -184,12 +184,33 @@ 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 + 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' }) From 74eadc41db370ba8dea9bb8c4bde44a6605f3c87 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 24 Sep 2025 14:09:29 +0000 Subject: [PATCH 3/3] Add comprehensive tests for empty string predicate handling Co-authored-by: scarroll32 <11340230+scarroll32@users.noreply.github.com> --- spec/ransack/search_spec.rb | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/spec/ransack/search_spec.rb b/spec/ransack/search_spec.rb index 7474043de..2a10bbd69 100644 --- a/spec/ransack/search_spec.rb +++ b/spec/ransack/search_spec.rb @@ -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 @@ -211,6 +211,33 @@ module Ransack 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' })