December 5, 2022
How to use Background Jobs with Ruby on Rails
Ruby on Rails is well known for its simplicity and convention over configuration design philosophy. One of the many powerful features of Rails is its support for background jobs, which allow you to run specific tasks asynchronously in the background without blocking the main thread of execution.
Background jobs are especially useful for tasks that take a long time to complete, such as sending emails, processing images, or generating reports. Using background jobs, you can ensure that your application remains responsive and can continue handling incoming requests even while these long-running tasks are being performed.
There are several different ways to implement background jobs in Ruby on Rails. The most common approach is using a gem such as Sidekiq, Delayed Job, or many more, which provides a simple and efficient way to manage your background jobs. These gems make it easy to enqueue jobs, set priorities, and track their progress.
To use a background job gem in your Ruby on Rails application, you must first add it to your
Gemfile and run
bundle install to install it. Then, you can create a new job by defining a class that inherits from the gem's Job class. For example, if you're using Sidekiq, you might create a new job like this:
rails generate job send_welcome_email
class SendWelcomeEmailJob < Sidekiq::Job def perform(user_id) user = User.find(user_id) UserMailer.welcome_email(user).deliver_later end end
This job will send a welcome email to the user with the specified ID. To enqueue this job, you can use the perform_async method like this:
This will enqueue the job and add it to the Sidekiq queue, where a worker will pick up and process it. The worker will then run the perform method of the job, which will send the email to the user.
To use Active Job, specify which queueing backend you want to use in your
config/application.rb file. For example, if you're using Sidekiq, you would add the following line to your configuration file:
config.active_job.queue_adapter = :sidekiq
Then, you can create a new job class that inherits from
ActiveJob::Base like this:
class SendWelcomeEmailJob < ActiveJob::Base queue_as :default def perform(user_id) user = User.find(user_id) UserMailer.welcome_email(user).deliver_later end end
This job is similar to the one we defined earlier, but it uses the Active Job framework instead of the Sidekiq gem. To enqueue this job, you can use the perform_later method like this:
This will add the job to the queue, where it will be picked up in order of priority.
If you recall from the snippet before, we added a
queue_as :default line to the
This instructs Sidekiq to give the job default priority, whereas other jobs could take more custom priorities dependent on what is registered inside your application.
To your needs, you can create custom queues relative. Each queuing provider will have different ways of establishing these, but for the most part, they are ad-hoc, meaning add them as necessary.
If using something like Sidekiq, it's best to pass as little data as possible through to the given job. Inside the job, you can add additional logic to look up records and perform those heavier tasks.
Additionally, if you make your job idempotent and transactional (a.k.a. capable of being run over and over).
Useful tips and tricks
Sometimes you might want to schedule your background job based on some form of the database entry timestamp. This can be saved as a datetime and referenced using a
set function. This means you can depend on a given timeframe to wait until the job gets executed or enter an arbitrary time delay with some simple ruby code.
GuestsCleanupJob.set(wait: 1.week).perform_later(guest) # arbitrary example
Another feature I like to make use of is callback functions. These are available for use depending on a given job's state. I'll commonly have a longer executing job running that might take some time to execute. When that job completes, I email an admin user letting them know that it has been done. It's a valuable way to set it and forget it, making your application work for you while you carry on focus elsewhere.
An example might look like the following, which would go inside your job file.
class MyNeatJob < ApplicationJob after_perform do UserMailer.with(admin_id: User.first).job_complete.deliver_later end # ... more logic end