Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 2 additions & 10 deletions lib/ransack/constants.rb
Original file line number Diff line number Diff line change
Expand Up @@ -161,16 +161,8 @@ module Constants
module_function
# replace % \ to \% \\
def escape_wildcards(unescaped)
case ActiveRecord::Base.connection.adapter_name
when "Mysql2".freeze
# Necessary for MySQL
unescaped.to_s.gsub(/([\\%_])/, '\\\\\\1')
when "PostGIS".freeze, "PostgreSQL".freeze
# Necessary for PostgreSQL
unescaped.to_s.gsub(/([\\%_.])/, '\\\\\\1')
else
unescaped
end
# Use ActiveRecord's sanitize_sql_like for consistent escaping across all adapters
ActiveRecord::Base.sanitize_sql_like(unescaped.to_s)
end
end
end
17 changes: 16 additions & 1 deletion lib/ransack/nodes/condition.rb
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,13 @@ def combinator_method
def format_predicate(attribute)
arel_pred = arel_predicate_for_attribute(attribute)
arel_values = formatted_values_for_attribute(attribute)
predicate = attr_value_for_attribute(attribute).public_send(arel_pred, arel_values)

# For LIKE predicates that use escaped wildcards, add ESCAPE clause for proper behavior
if needs_escape_clause?(arel_pred, arel_values)
predicate = attr_value_for_attribute(attribute).public_send(arel_pred, arel_values, '\\')
else
predicate = attr_value_for_attribute(attribute).public_send(arel_pred, arel_values)
end

if in_predicate?(predicate)
predicate.right = predicate.right.map do |pr|
Expand Down Expand Up @@ -364,6 +370,15 @@ def replace_right_node?(predicate)
def valid_combinator?
attributes.size < 2 || Constants::AND_OR.include?(combinator)
end

def needs_escape_clause?(arel_pred, arel_values)
# Add ESCAPE clause for LIKE predicates when values contain escaped wildcards
like_predicates = ['matches', 'does_not_match']
return false unless like_predicates.include?(arel_pred.to_s)

# Check if any values contain escaped wildcards (backslash followed by %, _, or \)
Array(arel_values).any? { |val| val.to_s.match?(/\\[%_\\]/) }
end
end
end
end
47 changes: 43 additions & 4 deletions spec/ransack/predicate_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,45 @@ module Ransack
end
end

describe 'wildcard escaping behavior' do
context 'with special characters in search values' do
it 'properly escapes wildcard characters in LIKE predicates' do
# Test that % and _ are treated as literal characters
Person.create!(name: '100% Pure')
Person.create!(name: '50_50 Blend')
Person.create!(name: 'Normal Text')

# Search for literal %
results = Person.ransack(name_cont: '%').result
expect(results).to include(Person.find_by(name: '100% Pure'))
expect(results).not_to include(Person.find_by(name: 'Normal Text'))

# Search for literal _
results = Person.ransack(name_cont: '_').result
expect(results).to include(Person.find_by(name: '50_50 Blend'))
expect(results).not_to include(Person.find_by(name: 'Normal Text'))
end

it 'works with start predicate' do
Person.create!(name: '%start')
Person.create!(name: 'normalstart')

results = Person.ransack(name_start: '%').result
expect(results).to include(Person.find_by(name: '%start'))
expect(results).not_to include(Person.find_by(name: 'normalstart'))
end

it 'works with end predicate' do
Person.create!(name: 'end%')
Person.create!(name: 'endnormal')

results = Person.ransack(name_end: '%').result
expect(results).to include(Person.find_by(name: 'end%'))
expect(results).not_to include(Person.find_by(name: 'endnormal'))
end
end
end

describe 'eq' do
it 'generates an equality condition for boolean true values' do
test_boolean_equality_for(true)
Expand Down Expand Up @@ -163,7 +202,7 @@ module Ransack
when "Mysql2"
/`people`.`name` LIKE '%\\\\%.\\\\_\\\\\\\\%'/
else
/"people"."name" LIKE '%%._\\%'/
/LIKE '%\\%\.\\_\\\\%' ESCAPE '\\'/
end) do
subject { @s }
end
Expand All @@ -183,7 +222,7 @@ module Ransack
when "Mysql2"
/`people`.`name` NOT LIKE '%\\\\%.\\\\_\\\\\\\\%'/
else
/"people"."name" NOT LIKE '%%._\\%'/
/NOT LIKE '%\\%\.\\_\\\\%' ESCAPE '\\'/
end) do
subject { @s }
end
Expand All @@ -205,7 +244,7 @@ module Ransack
when "Mysql2"
/LOWER\(`people`.`name`\) LIKE '%\\\\%.\\\\_\\\\\\\\%'/
else
/LOWER\("people"."name"\) LIKE '%%._\\%'/
/LIKE '%\\%\.\\_\\\\%' ESCAPE '\\'/
end) do
subject { @s }
end
Expand All @@ -227,7 +266,7 @@ module Ransack
when "Mysql2"
/LOWER\(`people`.`name`\) NOT LIKE '%\\\\%.\\\\_\\\\\\\\%'/
else
/LOWER\("people"."name"\) NOT LIKE '%%._\\%'/
/NOT LIKE '%\\%\.\\_\\\\%' ESCAPE '\\'/
end) do
subject { @s }
end
Expand Down
Loading