From cba79636cbe4942ef0a6d7d66b193f110d030ee9 Mon Sep 17 00:00:00 2001 From: Pranav Date: Tue, 14 Jul 2026 17:45:12 -0700 Subject: [PATCH] fix(captain): compare date custom attributes stored as strings in audience matcher Custom date attributes persist as ISO strings in jsonb, so is_greater_than/ is_less_than fell into the BigDecimal branch and never matched. Parse ISO date strings as times, mirroring Contacts::FilterService coercion. --- enterprise/app/services/captain/audience_matcher.rb | 10 ++++++++++ .../services/captain/audience_matcher_spec.rb | 7 +++++++ 2 files changed, 17 insertions(+) diff --git a/enterprise/app/services/captain/audience_matcher.rb b/enterprise/app/services/captain/audience_matcher.rb index 0fbc7c691..007960871 100644 --- a/enterprise/app/services/captain/audience_matcher.rb +++ b/enterprise/app/services/captain/audience_matcher.rb @@ -100,6 +100,8 @@ class Captain::AudienceMatcher def compare(actual, expected) return nil if actual.blank? + actual = Time.zone.parse(actual) if iso_date_string?(actual) + if actual.is_a?(Date) || actual.acts_like?(:time) actual.to_time <=> Time.zone.parse(expected.to_s) else @@ -109,6 +111,14 @@ class Captain::AudienceMatcher nil end + # Custom date attributes store ISO strings in jsonb; treat them as dates the way + # Contacts::FilterService does (it casts them in SQL). + def iso_date_string?(value) + value.is_a?(String) && Date.iso8601(value).present? + rescue ArgumentError + false + end + def older_than_days?(actual, days) date = to_date(actual) date.present? && date < Time.zone.today - days.to_i.days diff --git a/spec/enterprise/services/captain/audience_matcher_spec.rb b/spec/enterprise/services/captain/audience_matcher_spec.rb index 8f574f083..279e0439c 100644 --- a/spec/enterprise/services/captain/audience_matcher_spec.rb +++ b/spec/enterprise/services/captain/audience_matcher_spec.rb @@ -61,6 +61,13 @@ RSpec.describe Captain::AudienceMatcher do expect(matches?(leaf('created_at', 'days_before', '30'))).to be(true) expect(matches?(leaf('created_at', 'days_before', '60'))).to be(false) end + + it 'compares date custom attributes stored as ISO strings' do + contact.update!(custom_attributes: contact.custom_attributes.merge('signed_up_on' => '2024-01-15')) + expect(matches?(leaf('signed_up_on', 'is_greater_than', '2024-01-01'))).to be(true) + expect(matches?(leaf('signed_up_on', 'is_less_than', '2024-01-01'))).to be(false) + expect(matches?(leaf('signed_up_on', 'is_less_than', '2024-02-01'))).to be(true) + end end context 'with labels' do