ActiveJob provides first class support for retrying or discarding jobs based on certain exceptions. This works great if you have a static list of exceptions that you want to retry your jobs on and this would probably serve 99% use-cases for retry/discard. If you are on this boat, read the details on the official guide.

But if you want to dynamically retry the job based on a configuration, for example, from the database, this post is for you. For the purpose of this post, let’s assume we have a database model with the fields retry_max_attempts, retry_on_exceptions and retry_wait_seconds.

For this, we will use rescue_from but with StandardError. A disclaimer, if you are new to Rails, you probably shouldn’t do this unless it is for specific use-cases like the one in this post.

Here is how our rescue_on block should look like:

rescue_from(StandardError) do |exception|
  model = arguments.first
  raise exception if model.retry_max_attempts <= executions

  caught = model.retry_on_exceptions
                .map(&:safe_constantize).compact
                .any? { |klass| exception.is_a?(klass) }
  raise exception unless caught

  retry_job(:wait => model.retry_wait_seconds)
end

It is pretty self explanatory, but here’s the gist.

  1. We re-throw the exception if we have exceeded the max attempts (we are using the undocumented executions, so it is probable that this might break in the future, so you should have a good test coverage around this).
  2. We check if the retry_on_exceptions is the one we caught.
  3. If it is, we use retry_job with the correct wait.

One additional comment, if you are using Sidekiq, you would want to disable Sidekiq’s retry functionality by using the following in your job.

sidekiq_options :retry => 0