Fix date predicate formatter receiving String instead of Date object
Problem
After upgrading from Ransack 4.3.0 to 4.4.0, custom predicate formatters with type: :date started receiving String values instead of properly casted Date/Time objects. This caused errors when trying to call date methods like .end_of_day:
Ransack.configure do |config|
config.add_predicate 'lteq_eod',
arel_predicate: 'lteq',
formatter: proc { |value| value.end_of_day }, # Error: undefined method `end_of_day' for String
type: :date
end
Root Cause
The Value#cast method uses a Ruby case statement to match type values against symbols:
case type
when :date
cast_to_date(value)
when :datetime, :timestamp, :time, :timestamptz
cast_to_time(value)
else
cast_to_string(value) # String types fall through here!
end
When type is a String ("date") instead of a Symbol (:date), it doesn't match any of the when clauses and falls through to the else branch, which returns the original string value instead of a Date object.
Solution
Added defensive type conversion at the beginning of the Value#cast method to handle both string and symbol types:
def cast(type)
# Convert type to symbol if it's a string to ensure proper case matching
type = type.to_sym if type.is_a?(String)
case type
when :date
cast_to_date(value)
...
end
end
This ensures that string types like "date", "datetime", etc. are automatically converted to symbols before case matching, allowing them to be cast correctly.
Changes
- lib/ransack/nodes/value.rb: Added 3 lines to convert string types to symbols
- spec/ransack/nodes/value_spec.rb: Added test verifying string type
'date'works correctly - spec/ransack/predicate_spec.rb: Added comprehensive test for custom predicates with date formatters
Impact
- ✅ Minimal change: only 3 lines of production code
- ✅ Surgical fix: no changes to existing behavior for symbol types
- ✅ Backward compatible: handles both string and symbol types
- ✅ Well tested: comprehensive test coverage added
Example Usage
After this fix, the following code works correctly:
Ransack.configure do |config|
config.add_predicate 'lteq_eod',
arel_predicate: 'lteq',
formatter: proc { |value| value.end_of_day }, # value is now a Date
type: :date
end
# Usage in a search
@search = Person.ransack(life_start_lteq_eod: '2022-05-23')
The formatter now correctly receives a Date object and can call .end_of_day without errors.
Fixes #XXXX
Original prompt
This section details on the original issue you should resolve
<issue_title>Date predicate is formatted as string</issue_title> <issue_description>Hello,
in previous versions, block in this code use to receive a time object:
Ransack.configure do |config| config.add_predicate 'lteq_eod', arel_predicate: 'lteq', formatter: proc { |value| value.end_of_day }, type: :date endAfter upgrading from 4.3.0 to 4.4.0, the proc block started receiving an string.</issue_description>
Comments on the Issue (you are @copilot in this section)
💬 Share your feedback on Copilot coding agent for the chance to win a $200 gift card! Click here to start the survey.
@dcrec1 are you able to review this PR? https://github.com/activerecord-hackery/ransack/issues/1649#issuecomment-3368568273