Rails 5.2.1 was recently released, and released and it brought some improvements to ActiveJob (most of it was already in 5.1 really, the only thing added on 5.2 was the callback on discard_on), but lets keep in the subject here 😀
Recently I published a very basic post about ActiveJob, and these declarative exception handling tips will help to improve a lot your worker’s code!
For example, usually if one exception is rised during the job execution, the job will retry again later, but you might have some business exception that you wanna ignore and not try again, maybe something that says that your job is invalid.
I had one of these cases, some news that could be scheduled for delivery bug the reporter decided to delete it, so if the news was not found in the database the job should just ignore it, but when I implemented this I didn’t had this pretty helper, so I added a begin/rescue block to return and do nothing in the job, that code is ridiculous, but it was what I had available, it would be a lot better if I could just add something like this:
class NewsDeliveryJob < ActiveJob::Base discard_on(ActiveRecord::RecordNotFound) def perform(args) #... end end
This is a lot prettier than my version, because you can clearly see that the job should be discarded if a record is not found, and it is better than that, imagine that you want to discard the job, but also log why you did it, you can just pass a block to the discard_on method, like this:
class NewsDeliveryJob < ActiveJob::Base discard_on(ActiveRecord::RecordNotFound) do|job, exception| logger.error(exception) logger.log("job not retrying due to exception" end def perform(args) #... end end
You can also access the job properties in that blog, log what is not being retried, the sky is the limit!
You can also change how long the job will wait before retrying and how many times it will retry depending on your business criteria, to do that you’ll use the retry_on method, that will also receive an exception as he first parameter, and optionally a ‘wait’ and a ‘attempts’ options as you can see in the sample bellow:
class NewsDeliveryJob < ActiveJob::Base retry_on MyAppException # defaults to 3s wait, 5 attempts retry_on BusinessException, wait: ->(executions) { executions * 2 } retry_on ActiveRecord::Deadlocked, wait: 5.seconds, attempts: 3 retry_on Net::OpenTimeout, wait: :exponentially_longer, attempts: 10 discard_on ActiveJob::DeserializationError discard_on(ActiveRecord::RecordNotFound) do |job, exception| log.error(exception) log.error(job.inspect) end def perform(*args) # do your thing here end end
As you can see the configurations to retry can be really flexible (I cannot think right now in a job needing it all at once).
And another cool thing from rails 5.1 is that you can choose what job will be used to deliver your emails, of course you can just use the rails default email delivery job, but if you want to do some extra verification in your email before delivery, you can just set the delivery_job property in your mailer as you can see bellow:
class MyMailer < ApplicationMailer self.delivery_job = MyMailDeliveryJob ... end
After that, when you execute MyMailer.mail_method(paras).delivery_later, your MyMailDeliveryJob will be executed to deliver that email instead of the default one, this way you can even choose what queue name to use instead of the default ‘mailers’.
I think those were all the cool new stuff on the ActiveJob package, and I think those new features will help me a lot in the next project 😀
Please leave a comment if you have any question about this post, or if you want me to write about any specific subject in the future.