15

Anyone know of a clean way to avoid the ActiveJob::SerializationError that occurs when trying to serialize a Date or Time object?

The two solutions I've had so far are to:

  • Call Marshal/JSON/YAML dump when loading the arguments and then load back in the Job (which sucks because I need to monkey patch the mailer job)
  • Monkey patch Date and Time like so:

/lib/core_ext/time.rb

class Time

  include GlobalID::Identification

  def id
    self.to_i
  end

  def self.find(id)
    self.at(id.to_i)
  end
end

/lib/core_ext/date.rb

class Date

  include GlobalID::Identification

  def id
    self.to_time.id
  end

  def self.find(id)
    Time.find(id).to_date
  end
end

Which also sucks. Anyone have a better solution?

lsdr
  • 1,245
  • 1
  • 15
  • 28
kddeisz
  • 5,162
  • 3
  • 21
  • 44
  • 2
    Is it really necessary to pass only a Date or Time to the job (and why)? I think it would be better to pass an ActiveModel as a parameter to the job, possibly containing a Date or Time instance. (ActiveModels include GlobalID::Identification so they are serialisable) – sourcx Apr 16 '15 at 19:59
  • No, it's not really necessary. It's just convenient. And on top of that, it worked with DelayedJob before we integrated ActiveJob. So... it seems dumb that I would need to change my code in order to integrate with something that is simply supposed to abstract as opposed to change functionality. – kddeisz Apr 16 '15 at 21:51
  • You only enqueue the Date/Time object? Nothing else? AFAIK, ActiveJob actually calls Marshal to serialize your object and need something like the monkey patch you said to be able to retrieve and recall this object later. Can you post your Mailer? – lsdr Apr 23 '15 at 13:38
  • @kddeisz there is also the `WHITELIST` approach: http://stackoverflow.com/questions/27629697/monkeypatching-activejobs – lsdr Apr 23 '15 at 13:42
  • Could you give an example of how are you enqueuing the job? – Max Filippov Mar 13 '16 at 15:07

1 Answers1

4

Do you really need serialization? If it's just a Time/DateTime object, why not just encode and send your parameter as a Unix timestamp primitive?

>> tick = Time.now
=> 2016-03-30 01:19:52 -0400

>> tick_unix = tick.to_i
=> 1459315192

# Send tick_unix as the param...

>> tock = Time.at(tick_unix)
=> 2016-03-30 01:19:52 -0400

Note this will be accurate to within one second. If you need 100% exact accuracy you'll need to convert the time to a Rational and pass both the numerator and denominator as params and then call Time.at(Rational(numerator, denominator) within the job.

>> tick = Time.now
=> 2016-03-30 01:39:10 -0400

>> tick_rational = tick.to_r
=> (1459316350224979/1000000)

>> numerator_param = tick_rational.numerator
=> 1459316350224979

>> denominator_param = tick_rational.denominator
=> 1000000

# On the other side of the pipe...

>> tock = Time.at(Rational(numerator_param, denominator_param))
=> 2016-03-30 01:39:10 -0400

>> tick == tock
=> true
Anthony E
  • 11,072
  • 2
  • 24
  • 44
  • I know there are plenty of ways to do it by changing the value, I was just wondering if there was a way to do it in conjunction with the serialization of the other objects. As in being able to pass `my_method(date, time, model)` and have it work. At this point it's pretty obsolete though because this question was asked almost a year ago. – kddeisz Mar 30 '16 at 13:39