Skip to content

Commit 074d431

Browse files
committed
Fix invalid durations in ActiveJob serialization
Durations that were round-tripped through ActiveJob::Arguments.serialize would appear fine at a first glance, but trying to perform duration-math on them would fail: ``` irb(main):001:0> d = ActiveJob::Arguments.deserialize(ActiveJob::Arguments.serialize([1.year]))[0] => 1 year irb(main):002:0> d + 1.day activesupport-6.1.4.4/lib/active_support/duration.rb:242:in `+': undefined method `merge' for [[:years, 1]]:Array (NoMethodError) ```
1 parent b6669e5 commit 074d431

File tree

3 files changed

+22
-2
lines changed

3 files changed

+22
-2
lines changed

activejob/CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
1+
* Fix deserialization of ActiveSupport::Duration
2+
3+
Previously, a deserialized Duration would return an array from Duration#parts.
4+
It will now return a hash just like a regular Duration.
5+
6+
This also fixes an error when trying to add or subtract from a deserialized Duration
7+
(eg `duration + 1.year`).
8+
9+
*Jonathan del Strother*
10+
111
* `perform_enqueued_jobs` is now compatible with all Active Job adapters
212

313
This means that methods that depend on it, like Action Mailer's `assert_emails`,

activejob/lib/active_job/serializers/duration_serializer.rb

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,16 @@ module ActiveJob
44
module Serializers
55
class DurationSerializer < ObjectSerializer # :nodoc:
66
def serialize(duration)
7+
# Ideally duration.parts would be wrapped in an array before passing to Arguments.serialize,
8+
# but we continue passing the bare hash for backwards compatibility:
79
super("value" => duration.value, "parts" => Arguments.serialize(duration.parts))
810
end
911

1012
def deserialize(hash)
1113
value = hash["value"]
1214
parts = Arguments.deserialize(hash["parts"])
13-
14-
klass.new(value, parts)
15+
# `parts` is originally a hash, but will have been flattened to an array by Arguments.serialize
16+
klass.new(value, parts.to_h)
1517
end
1618

1719
private

activejob/test/cases/argument_serialization_test.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
require "active_job/arguments"
66
require "models/person"
77
require "active_support/core_ext/hash/indifferent_access"
8+
require "active_support/core_ext/integer/time"
9+
require "active_support/duration"
810
require "jobs/kwargs_job"
911
require "jobs/arguments_round_trip_job"
1012
require "support/stubs/strong_parameters"
@@ -188,6 +190,12 @@ def self.permitted?
188190
end
189191
end
190192

193+
test "should maintain a functional duration" do
194+
duration = perform_round_trip([1.year]).first
195+
assert_kind_of Hash, duration.parts
196+
assert_equal 2.years, duration + 1.year
197+
end
198+
191199
test "should disallow non-string/symbol hash keys" do
192200
assert_raises ActiveJob::SerializationError do
193201
ActiveJob::Arguments.serialize [ { 1 => 2 } ]

0 commit comments

Comments
 (0)