Add support for timezones to Active Job

Record what was the current timezone in effect when the job was
enqueued and then restore when the job is executed in same way
that the current locale is recorded and restored.
This commit is contained in:
Andrew White 2018-02-22 14:14:42 +00:00
parent 9c0c90979a
commit a9d1167b1f
10 changed files with 103 additions and 1 deletions

View File

@ -1,5 +1,13 @@
## Rails 6.0.0.alpha (Unreleased) ## ## Rails 6.0.0.alpha (Unreleased) ##
* Add support for timezones to Active Job
Record what was the current timezone in effect when the job was
enqueued and then restore when the job is executed in same way
that the current locale is recorded and restored.
*Andrew White*
* Rails 6 requires Ruby 2.4.1 or newer. * Rails 6 requires Ruby 2.4.1 or newer.
*Jeremy Daer* *Jeremy Daer*

View File

@ -9,6 +9,7 @@ require "active_job/execution"
require "active_job/callbacks" require "active_job/callbacks"
require "active_job/exceptions" require "active_job/exceptions"
require "active_job/logging" require "active_job/logging"
require "active_job/timezones"
require "active_job/translation" require "active_job/translation"
module ActiveJob #:nodoc: module ActiveJob #:nodoc:
@ -68,6 +69,7 @@ module ActiveJob #:nodoc:
include Callbacks include Callbacks
include Exceptions include Exceptions
include Logging include Logging
include Timezones
include Translation include Translation
ActiveSupport.run_load_hooks(:active_job, self) ActiveSupport.run_load_hooks(:active_job, self)

View File

@ -31,6 +31,9 @@ module ActiveJob
# I18n.locale to be used during the job. # I18n.locale to be used during the job.
attr_accessor :locale attr_accessor :locale
# Timezone to be used during the job.
attr_accessor :timezone
end end
# These methods will be included into any Active Job object, adding # These methods will be included into any Active Job object, adding
@ -87,7 +90,8 @@ module ActiveJob
"priority" => priority, "priority" => priority,
"arguments" => serialize_arguments(arguments), "arguments" => serialize_arguments(arguments),
"executions" => executions, "executions" => executions,
"locale" => I18n.locale.to_s "locale" => I18n.locale.to_s,
"timezone" => Time.zone.try(:name)
} }
end end
@ -125,6 +129,7 @@ module ActiveJob
self.serialized_arguments = job_data["arguments"] self.serialized_arguments = job_data["arguments"]
self.executions = job_data["executions"] self.executions = job_data["executions"]
self.locale = job_data["locale"] || I18n.locale.to_s self.locale = job_data["locale"] || I18n.locale.to_s
self.timezone = job_data["timezone"] || Time.zone.try(:name)
end end
private private

View File

@ -0,0 +1,13 @@
# frozen_string_literal: true
module ActiveJob
module Timezones #:nodoc:
extend ActiveSupport::Concern
included do
around_perform do |job, block, _|
Time.use_zone(job.timezone, &block)
end
end
end
end

View File

@ -54,4 +54,11 @@ class JobSerializationTest < ActiveSupport::TestCase
job.provider_job_id = "some value set by adapter" job.provider_job_id = "some value set by adapter"
assert_equal job.provider_job_id, job.serialize["provider_job_id"] assert_equal job.provider_job_id, job.serialize["provider_job_id"]
end end
test "serialize stores the current timezone" do
Time.use_zone "Hawaii" do
job = HelloJob.new
assert_equal "Hawaii", job.serialize["timezone"]
end
end
end end

View File

@ -0,0 +1,24 @@
# frozen_string_literal: true
require "helper"
require "jobs/timezone_dependent_job"
class TimezonesTest < ActiveSupport::TestCase
setup do
JobBuffer.clear
end
test "it performs the job in the given timezone" do
job = TimezoneDependentJob.new("2018-01-01T00:00:00Z")
job.timezone = "London"
job.perform_now
assert_equal "Happy New Year!", JobBuffer.last_value
job = TimezoneDependentJob.new("2018-01-01T00:00:00Z")
job.timezone = "Eastern Time (US & Canada)"
job.perform_now
assert_equal "Just 5 hours to go", JobBuffer.last_value
end
end

View File

@ -110,6 +110,22 @@ class QueuingTest < ActiveSupport::TestCase
end end
end end
test "current timezone is kept while running perform_later" do
skip if adapter_is?(:inline)
begin
current_zone = Time.zone
Time.zone = "Hawaii"
TestJob.perform_later @id
wait_for_jobs_to_finish_for(5.seconds)
assert job_executed
assert_equal "Hawaii", job_executed_in_timezone
ensure
Time.zone = current_zone
end
end
test "should run job with higher priority first" do test "should run job with higher priority first" do
skip unless adapter_is?(:delayed_job, :que) skip unless adapter_is?(:delayed_job, :que)

View File

@ -0,0 +1,22 @@
# frozen_string_literal: true
require_relative "../support/job_buffer"
class TimezoneDependentJob < ActiveJob::Base
def perform(now)
now = now.in_time_zone
new_year = localtime(2018, 1, 1)
if now >= new_year
JobBuffer.add("Happy New Year!")
else
JobBuffer.add("Just #{(new_year - now).div(3600)} hours to go")
end
end
private
def localtime(*args)
Time.zone ? Time.zone.local(*args) : Time.utc(*args)
end
end

View File

@ -21,6 +21,7 @@ class TestJob < ActiveJob::Base
File.open(Rails.root.join("tmp/\#{x}.new"), "wb+") do |f| File.open(Rails.root.join("tmp/\#{x}.new"), "wb+") do |f|
f.write Marshal.dump({ f.write Marshal.dump({
"locale" => I18n.locale.to_s || "en", "locale" => I18n.locale.to_s || "en",
"timezone" => Time.zone.try(:name) || "UTC",
"executed_at" => Time.now.to_r "executed_at" => Time.now.to_r
}) })
end end

View File

@ -62,4 +62,8 @@ module TestCaseHelpers
def job_executed_in_locale(id = @id) def job_executed_in_locale(id = @id)
job_data(id)["locale"] job_data(id)["locale"]
end end
def job_executed_in_timezone(id = @id)
job_data(id)["timezone"]
end
end end