Search code examples
ruby-on-railsdelayed-jobrescue

Rescue "RecordNotFound" errors in DelayedJob workers


I use DelayedJob to handle certain tasks in the background.

For example, in my application a user can "like" a message, and when that happens the poster gets notified. This notification is handled in the background.

Occasionally, what can happen is that the liker decides to undo his action and delete his "like" before the notification goes out. In those instances the background code hits a "RecordNotFound" error as the "like" no longer exists.

I thought I handled this case by rescuing the error as so (self here is the Like):

  def send_push_notifications
    begin
      user = self.message.user
      message = "#{self.user.name} liked your workout"
      Urbanairship::push_now(user, message, ["message", self.message.id]) if self.user != user
    rescue ActiveRecord::RecordNotFound
      # Do nothing
    end
  end

However, in practice this does not seem to be rescuing the errors, as I still see such errors in my logs:

{ActiveRecord::RecordNotFound, class: Like , primary key: 1557 
/app/vendor/bundle/ruby/1.9.1/gems/delayed_job-3.0.2/lib/delayed/serialization/active_record.rb:12:in `rescue in yaml_new'
/app/vendor/bundle/ruby/1.9.1/gems/delayed_job-3.0.2/lib/delayed/serialization/active_record.rb:6:in `yaml_new'
/usr/local/lib/ruby/1.9.1/syck.rb:135:in `transfer'
/usr/local/lib/ruby/1.9.1/syck.rb:135:in `node_import'
/usr/local/lib/ruby/1.9.1/syck.rb:135:in `load'
/usr/local/lib/ruby/1.9.1/syck.rb:135:in `load'

Any ideas why my rescue statement is not working in this case?


Solution

  • I finally solved this by using a class method instead of an instance method for my delay calls. Let me show you by way of example. Here's how I previously structured my delay calls:

      def background_send_push_notifications
        self.delay.send_push_notifications
      end
    
      def send_push_notifications
        message = "#{self.user.name} liked your workout"
        ...
      end
    

    The issue I kept hitting was that it was common for a user to immediately unlike, right after liking something. This meant that the Like object was no more when the delayed_job tried to execute, and I'd get lots of "RecordNotFound" errors.

    Now I have transitioned the delay call to a class method that does the object lookup in the background and returns if it no longer exists. Here's the new structure

      def background_send_push_notifications
        Like.delay.send_push_notifications(self.id)
      end
    
      def self.send_push_notifications(id)
        like = Like.find_by_id(id)
        like.send_push_notifications unless like.nil?
      end
    
      def send_push_notifications
        message = "#{self.user.name} liked your workout"
        ...
      end
    

    Hope this helps somebody!