Search code examples
ruby-on-railsrubyrails-activerecorddelayed-jobaasm

delayed_job on ActiveRecord model with AASM - failing method error swallowed, throws "wrong number of arguments" instead


In my Rails 4 app, I have an ActiveRecord model with an AASM column. When I use a failing method with delayed_job_active_record, it swallows the error and throws something else to do with AASM.

class MyModel < ActiveRecord::Base
  include AASM

  aasm do
    # aasm setup here
  end

  def self.joberror
    1/0 #bad code here
  end
end

MyModel.joberror fails predictably with a ZeroDivisionError. However, when I run this method from inside delayed_job_active_record (MyModel.delay.joberror), the correct error is swallowed, and this is thrown instead:

wrong number of arguments (2 for 0)
/Users/myhome/myproject/.gems/gems/aasm-4.0.5/lib/aasm/persistence/base.rb:67:in `block (2 levels) in state_with_scope'

full stacktrace here

That points to code in the aasm gem, despite this method not involving aasm at all, nor even an actual instance of MyModel. However, if I comment out the aasm code it goes back to the correct ZeroDivision error.

I've read that delayed_job does some serialization of the method you want it to perform but I don't understand the consequences of that enough to know why it would cause this or how to fix it.


Solution

  • This delayed_job code was the problem. When delayed_job encounters an error it looks for something on the object that it can call, in this case anything named "error":

    # lib/delayed/backend/base.rb: 96    
    rescue => e
      hook :error, e
    raise e
    
    # lib/delayed/backend/base.rb: 111
    def hook(name, *args)
      if payload_object.respond_to?(name) # <--- my object has an unrelated method "error"
        method = payload_object.method(name)
        method.arity == 0 ? method.call : method.call(self, *args)
      end
    rescue DeserializationError
    end
    

    In my case I had an aasm state named error. So whenever an error occurred, delayed_job rescued and then tried to call this error state as a method, which failed. This obviously only occurred when the code being called threw an error, and only when it was run via delayed_job.