rails: Mail and deliver_later doesn't work with date argument

If you pass :date into mail with a deliver_later:

mail(:from => "test@test.com", :to => "another@another.com", :subject => "hey", :date => Time.now).deliver_later

Then it throws a SerializationError, because Time is an unsupported argument type:

http://edgeapi.rubyonrails.org/classes/ActiveJob/SerializationError.html

About this issue

  • Original URL
  • State: closed
  • Created 9 years ago
  • Reactions: 5
  • Comments: 23 (7 by maintainers)

Most upvoted comments

This is still a real problem and is not working for me in Rails 4.2.6

MyMailer.my_action(Time.now).deliver_later yields an error ActiveJob::SerializationError: Unsupported argument type: Time

If you don’t mind loosing millisecond accuracy and you are looking for a quick and dirty fix: create an initializer with the following content:

# https://github.com/rails/rails/issues/18519
class ActiveSupport::TimeWithZone
  include GlobalID::Identification

  alias_method :id, :to_i
  def self.find(seconds_since_epoch)
    Time.zone.at(seconds_since_epoch.to_i)
  end
end

class Time
  include GlobalID::Identification

  alias_method :id, :to_i
  def self.find(seconds_since_epoch)
    Time.at(seconds_since_epoch.to_i)
  end
end

ActiveJob supports anything than includes GlobalID::Identification. This patch adds it to Time:

Time.now.to_global_id.to_s
# => "gid://your-app-name/Time/1490354247"

GlobalID::Locator.locate "gid://your-app-name/Time/1490349356"
# => 2017-03-24 11:55:56 +0200

If you want millisecond accuracy

class ActiveSupport::TimeWithZone
  include GlobalID::Identification

  def id
    (Time.zone.now.to_f * 1000).round
  end

  def self.find(milliseconds_since_epoch)
    Time.zone.at(milliseconds_since_epoch.to_f / 1000)
  end
end

class Time
  include GlobalID::Identification

  def id
    (Time.zone.now.to_f * 1000).round
  end

  def self.find(milliseconds_since_epoch)
    Time.zone.at(milliseconds_since_epoch.to_f / 1000)
  end
end


class Date
  include GlobalID::Identification

  alias_method :id, :to_s
  def self.find(date_string)
    Date.parse(date_string)
  end
end

@CodingAnarchy support has been added https://github.com/rails/rails/pull/32026 but it didn’t make it into 5.2 so it will be 6.0

Just ran into this myself. Very strange design decision to not support Date serialization.

And for Date too:

class Date
  include GlobalID::Identification

  alias_method :id, :to_s
  def self.find(date_string)
    Date.parse(date_string)
  end
end

Going to throw in my 2 cents here - it’s apparently trivial to support this sort of serialization, so I cannot fathom a reason to force everyone to monkey patch in GlobalID to do it. But I, too, ran into this problem as a surprise.

There should be a symmetry between deliver_now and deliver_later. If parameters are accepted in one, they should be accepted in the other.

Also, shouldn’t the adapter itself do the enforcing of the parameter? If it doesn’t support Time object it can raise an error?

@vnnoder I believe you need to change your definition of id on time to be

def id
  (to_f * 1000).round
end

or use @nickstanish suggestion

def id
  iso8601
end

Is there a way to render the e-mail immediately, and only delay the sending of the e-mail. This would allow something like

MyMailer.my_action(Time.now).deliver_later

to work just fine without serializing Date. If the full message, and not just the body is rendered like this, then even the original code segment would work:

mail(
  :from => "test@test.com",
  :to => "another@another.com",
  :subject => "hey",
  :date => Time.now
).deliver_later