Skip to content

Commit e1dff0a

Browse files
authored
Merge pull request #652 from afuno/feature/add_date_in_enumerators
Serialize :date column type in cursor to ISO8601 string format
2 parents a69702c + 748e0d4 commit e1dff0a

File tree

6 files changed

+65
-5
lines changed

6 files changed

+65
-5
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ nil
1414

1515
### Bug fixes
1616

17-
nil
17+
- [652](https://github.com/Shopify/job-iteration/pull/652) Fix ISO8601 serialization for `Date` columns in ActiveRecord enumerators.
1818

1919
## v1.11.0 (Jul 14, 2025)
2020

lib/job-iteration/active_record_batch_enumerator.rb

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -120,10 +120,15 @@ def serialize_column_values!(column_values)
120120
end
121121

122122
def column_value(value)
123-
return value unless value.is_a?(Time)
124-
125-
value = value.in_time_zone(@timezone) unless @timezone.nil?
126-
value.strftime(SQL_DATETIME_WITH_NSEC)
123+
case value
124+
when Time
125+
value = value.in_time_zone(@timezone) unless @timezone.nil?
126+
value.strftime(SQL_DATETIME_WITH_NSEC)
127+
when Date
128+
value.iso8601
129+
else
130+
value
131+
end
127132
end
128133
end
129134
end

lib/job-iteration/active_record_enumerator.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ def column_value(record, attribute)
7070
when :datetime
7171
value = value.in_time_zone(@timezone) unless @timezone.nil?
7272
value.strftime(SQL_DATETIME_WITH_NSEC)
73+
when :date
74+
value.iso8601
7375
else
7476
value
7577
end

test/test_helper.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ class Order < ActiveRecord::Base
3535
self.primary_key = [:shop_id, :id]
3636
end
3737

38+
class Event < ActiveRecord::Base
39+
end
40+
3841
mysql_host = ENV.fetch("MYSQL_HOST") { "localhost" }
3942
mysql_port = ENV.fetch("MYSQL_PORT") { 3306 }
4043

@@ -84,6 +87,11 @@ class Order < ActiveRecord::Base
8487
t.integer(:shop_id)
8588
t.string(:name)
8689
end
90+
91+
create_table(:events, force: true) do |t|
92+
t.string(:name)
93+
t.date(:occurred_on)
94+
end
8795
end
8896
end
8997

@@ -157,6 +165,7 @@ def truncate_fixtures
157165
ActiveRecord::Base.connection.truncate(Product.table_name)
158166
ActiveRecord::Base.connection.truncate(Comment.table_name)
159167
ActiveRecord::Base.connection.truncate(Order.table_name)
168+
ActiveRecord::Base.connection.truncate(Event.table_name)
160169
end
161170

162171
def with_global_default_retry_backoff(backoff)

test/unit/active_record_batch_enumerator_test.rb

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,28 @@ class ActiveRecordBatchEnumeratorTest < IterationUnitTest
118118
assert_equal([shops, cursor], enum.first)
119119
end
120120

121+
test "columns with date type are serialized to ISO8601 format" do
122+
Event.create!(name: "Conference", occurred_on: Date.new(2025, 10, 15))
123+
Event.create!(name: "Workshop", occurred_on: Date.new(2025, 10, 20))
124+
125+
enum = build_enumerator(relation: Event.all, columns: [:occurred_on])
126+
events = Event.order(:occurred_on).take(2)
127+
128+
assert_equal([events, "2025-10-20"], enum.first)
129+
end
130+
131+
test "cursor can be used to resume on date column" do
132+
Event.create!(name: "Event 1", occurred_on: Date.new(2025, 1, 10))
133+
Event.create!(name: "Event 2", occurred_on: Date.new(2025, 1, 20))
134+
Event.create!(name: "Event 3", occurred_on: Date.new(2025, 1, 30))
135+
136+
enum = build_enumerator(relation: Event.all, columns: [:occurred_on, :id], cursor: ["2025-01-10", Event.first.id])
137+
events = Event.order(:occurred_on, :id).offset(1).take(2)
138+
139+
cursor = [events.last.occurred_on.iso8601, events.last.id]
140+
assert_equal([events, cursor], enum.first)
141+
end
142+
121143
test "cursor can be used to resume on multiple columns" do
122144
enum = build_enumerator(columns: [:created_at, :id])
123145
products = Product.order(:created_at, :id).take(2)

test/unit/active_record_enumerator_test.rb

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,28 @@ def next_batch(*)
134134
assert_equal([shops, cursor], enum.first)
135135
end
136136

137+
test "columns with date type are serialized to ISO8601 format" do
138+
Event.create!(name: "Conference", occurred_on: Date.new(2025, 10, 15))
139+
Event.create!(name: "Workshop", occurred_on: Date.new(2025, 10, 20))
140+
141+
enum = build_enumerator(relation: Event.all, columns: [:occurred_on]).batches
142+
events = Event.order(:occurred_on).take(2)
143+
144+
assert_equal([events, "2025-10-20"], enum.first)
145+
end
146+
147+
test "cursor can be used to resume on date column" do
148+
Event.create!(name: "Event 1", occurred_on: Date.new(2025, 1, 10))
149+
Event.create!(name: "Event 2", occurred_on: Date.new(2025, 1, 20))
150+
Event.create!(name: "Event 3", occurred_on: Date.new(2025, 1, 30))
151+
152+
enum = build_enumerator(relation: Event.all, columns: [:occurred_on, :id], cursor: ["2025-01-10", Event.first.id]).batches
153+
events = Event.order(:occurred_on, :id).offset(1).take(2)
154+
155+
cursor = [events.last.occurred_on.iso8601, events.last.id]
156+
assert_equal([events, cursor], enum.first)
157+
end
158+
137159
test "#size returns the number of items in the relation" do
138160
enum = build_enumerator(relation: Product.all)
139161

0 commit comments

Comments
 (0)