Recover precision when serializing `Time`, `TimeWithZone` and `DateTime`.

This commit is contained in:
Guo Xiang Tan 2020-06-22 22:09:14 +08:00 committed by Rafael Mendonça França
parent 55cfdaf1c0
commit bda10bf3a8
No known key found for this signature in database
GPG Key ID: FC23B6D0F1EEE948
10 changed files with 43 additions and 52 deletions

View File

@ -1,3 +1,7 @@
* Recover nano precision when serializing `Time`, `TimeWithZone` and `DateTime` objects.
*Alan Tan*
* Deprecate `config.active_job.return_false_on_aborted_enqueue`.
*Rafael Mendonça França*

View File

@ -9,6 +9,7 @@ module ActiveJob
extend ActiveSupport::Autoload
autoload :ObjectSerializer
autoload :TimeObjectSerializer
autoload :SymbolSerializer
autoload :DurationSerializer
autoload :DateTimeSerializer

View File

@ -2,11 +2,7 @@
module ActiveJob
module Serializers
class DateTimeSerializer < ObjectSerializer # :nodoc:
def serialize(time)
super("value" => time.iso8601)
end
class DateTimeSerializer < TimeObjectSerializer # :nodoc:
def deserialize(hash)
DateTime.iso8601(hash["value"])
end

View File

@ -0,0 +1,13 @@
# frozen_string_literal: true
module ActiveJob
module Serializers
class TimeObjectSerializer < ObjectSerializer # :nodoc:
NANO_PRECISION = 9
def serialize(time)
super("value" => time.iso8601(NANO_PRECISION))
end
end
end
end

View File

@ -2,11 +2,7 @@
module ActiveJob
module Serializers
class TimeSerializer < ObjectSerializer # :nodoc:
def serialize(time)
super("value" => time.iso8601)
end
class TimeSerializer < TimeObjectSerializer # :nodoc:
def deserialize(hash)
Time.iso8601(hash["value"])
end

View File

@ -2,11 +2,7 @@
module ActiveJob
module Serializers
class TimeWithZoneSerializer < ObjectSerializer # :nodoc:
def serialize(time)
super("value" => time.iso8601)
end
class TimeWithZoneSerializer < TimeObjectSerializer # :nodoc:
def deserialize(hash)
Time.iso8601(hash["value"]).in_time_zone
end

View File

@ -671,20 +671,6 @@ module ActiveJob
at_range = arguments[:at] - 1..arguments[:at] + 1
arguments[:at] = ->(at) { at_range.cover?(at) }
end
arguments[:args] = round_time_arguments(arguments[:args]) if arguments[:args]
end
end
def round_time_arguments(argument)
case argument
when Time, ActiveSupport::TimeWithZone, DateTime
argument.change(usec: 0)
when Hash
argument.transform_values { |value| round_time_arguments(value) }
when Array
argument.map { |element| round_time_arguments(element) }
else
argument
end
end

View File

@ -20,16 +20,19 @@ class ArgumentSerializationTest < ActiveSupport::TestCase
[ nil, 1, 1.0, 1_000_000_000_000_000_000_000,
"a", true, false, BigDecimal(5),
:a, 1.day, Date.new(2001, 2, 3), Time.new(2002, 10, 31, 2, 2, 2, "+02:00"),
DateTime.new(2001, 2, 3, 4, 5, 6, "+03:00"),
ActiveSupport::TimeWithZone.new(Time.utc(1999, 12, 31, 23, 59, 59), ActiveSupport::TimeZone["UTC"]),
:a,
1.day,
Date.new(2001, 2, 3),
Time.new(2002, 10, 31, 2, 2, 2.123456789r, "+02:00"),
DateTime.new(2001, 2, 3, 4, 5, 6.123456r, "+03:00"),
ActiveSupport::TimeWithZone.new(Time.utc(1999, 12, 31, 23, 59, "59.123456789".to_r), ActiveSupport::TimeZone["UTC"]),
[ 1, "a" ],
{ "a" => 1 },
ModuleArgument,
ModuleArgument::ClassArgument,
ClassArgument
].each do |arg|
test "serializes #{arg.class} - #{arg} verbatim" do
test "serializes #{arg.class} - #{arg.inspect} verbatim" do
assert_arguments_unchanged arg
end
end

View File

@ -608,6 +608,21 @@ class EnqueuedJobsTest < ActiveJob::TestCase
end
end
def test_assert_enqueued_with_time_and_time_precision
time_with_zone = ActiveSupport::TimeWithZone.new(
Time.utc(1999, 12, 31, 23, 59, "59.123456789".to_r),
ActiveSupport::TimeZone["Tokyo"]
)
time = Time.at(946702800, 1234567, :nanosecond)
date_time = DateTime.new(2001, 2, 3, 4, 5, 6.123456, "+03:00")
args = [{ argument1: [time_with_zone, time, date_time] }]
assert_enqueued_with(job: MultipleKwargsJob, args: args) do
MultipleKwargsJob.perform_later(argument1: [time_with_zone, time, date_time])
end
end
def test_assert_enqueued_with_with_no_block_args
assert_raise ArgumentError do
NestedJob.set(wait_until: Date.tomorrow.noon).perform_later

View File

@ -1768,25 +1768,6 @@ class ProductTest < ActiveSupport::TestCase
end
```
### Asserting Time Arguments in Jobs
When serializing job arguments, `Time`, `DateTime`, and `ActiveSupport::TimeWithZone` lose microsecond precision. This means comparing deserialized time with actual time doesn't always work. To compensate for the loss of precision, `assert_enqueued_with` and `assert_performed_with` will remove microseconds from time objects in argument assertions.
```ruby
require "test_helper"
class ProductTest < ActiveSupport::TestCase
include ActiveJob::TestHelper
test "that product is reserved at a given time" do
now = Time.now
assert_performed_with(job: ReservationJob, args: [product, now]) do
product.reserve(now)
end
end
end
```
Testing Action Cable
--------------------