New Rails Shiny: ActiveJob

Share this article

active-job

One highly anticipated feature of Rails 4.2 is the introduction of the ActiveJob library. Like many components in Rails, ActiveJob serves as the adapter layer on a few of the most popular queuing libraries in the Ruby ecosystem.

With the new ActiveJob library, choosing a queuing library with an unique API will not be something to worry about anymore. Rails now provides a unique queuing interface which allows you to swap out queueing gems to your heart’s desire, without changing your application code. Want to switch from Delayed Job to Backburner? Not a problem. ActiveJob let’s you do that with minimal pain.

Another great feature is complete integration with ActionMailer. Email is always one of those tasks that can be done without the user needing to know the email was sent. ActiveJob also provides the same level of abstraction with the methods deliver_later! and deliver_now!, with the obvious functionality.

Lastly, with the addition of ActiveJob, Rails 4.2 will also include the Global ID library, which provides a unique identifier of a model instance: gid://Application/Model/id. This is particularly useful in job scheduling, since we need to reference a model object rather than the serialized object itself. Instead of the scheduler being involved with the particular model and it’s ID, the scheduler just needs to use the global ID to find the exact model instance.

To fully understand the ActiveJob feature set, we need to take a look at it’s core ActiveJob functionality, the ActionMailer functionality, and Global ID.

Using ActiveJob

For this example, we will be utilizing a Rails 4.2 application (4.2.0.beta2 to be exact) that deals with geolocation. In the application, there is an Account model with fields for zipcode, city, state, latitude, and longitude. Since zipcode yields the results for the other items in the list, this is the only one our application will request from the user.

Below is an example of our account class:

{
    "id": null,
    "name":  null,
    "city": null,
    "state": null,
    "zipcode": null,
    "latitude": null,
    "longitude": null,
    "created_at": null,
    "updated_at": null
}

Without a job based system, the simple task of finding this additional data might be too slow of a transaction for the application to do in real time. Typically, the application must go out to a third party service to gather the data. This is the perfect scenario for a background job.

Let’s create our first job for this action!

First, be sure to add gem 'delayed_job_active_record' to your gemfile, run bundle install in the terminal, and follow the directions on the delayed_job github page to fully install Delayed Job.

Once Delayed Job (or your queuing system of choice) is installed, you’ll need to generate the job class from the terminal. By using the following command, a new file will appear in the app/jobs folder:

$ rails g job geolocate_account
create  app/jobs/geolocate_account_job.rb

The above command generates the following class:

class GeolocateAccountJob < ActiveJob::Base
  queue_as :default

  def perform(*args)
    # Do something later
  end
end

In the above class, the perform action is where all the logic for the task will live. For our application, we want to find the geolocation from the zipcode. We will utilize the geocoder gem, which gives us an API to connect to a third parties geolocation service. Here’s the code:

def perform(account)
  result = Geocoder.search(account.zipcode).first

  if result
    account.latitude = result.latitude
    account.longitude = result.longitude
    account.city = result.city
    account.state = result.state
    account.save!
  end
end

Now that we have our job’s perform method complete. Let’s open the console and call this job to get our first account queued up. Since our application is fresh, let’s add a new account first:

Account.create(name: 'Dunder Mifflin', zipcode: '18505')

And then queue up the account with our GeolocateAccountJob:

GeolocateAccountJob.perform_later Account.first

You can see that the job has successfully been enqueued by running Delayed::Job.count. You can also review your logs and see that the job has successfully committed to the database.

Queueing for the Future

Sometimes it is important to be able to queue a job in the future and ActiveJob let’s you do just that. For example, the vanilla way of queueing a job is as follows:

GeolocateAccountJob.perform_later Account.first

However, you also have the ability to queue jobs for the future:

GeolocateAccountJob.set(wait: Date.tomorrow.noon).perform_later Account.first

and

GeolocateAccountJob.set(wait_until: 1.day).perform_later Account.first

Both of these code snippets will yield the about same results.

You can also set a priority to your queue jobs:

GeolocateAccountJob.set(queue: :low_priority).perform_later Account.first

Rescuing from Failure

Another great feature of ActiveJob is the ability to catch exceptions that happen in your perform method. Using the same GeolocateAccountJob class, let’s modify our perform method to throw a ServiceDown exception:

class GeolocateAccountJob < ActiveJob::Base
  queue_as :default

  def perform(account)
     raise Geolocation::ServiceDown
  end
end

We can then add a rescue block to handle our exception:

class GeolocateAccountJob < ActiveJob::Base
  queue_as :default

  rescue_from(Geolocation::ServiceDown) do |exception|
    # Handle failed exception
  end

  def perform(account)
     raise Geolocation::ServiceDown
  end
end

Integration with ActionMailer

Another beneficial feature that has been included in ActiveJob is integration with ActionMailer. Sending email is definitely a function that should be performed via a queue. Instead of using your selected library’s Mailer methods, use the two that ActiveJob natively supports deliver_now! and deliver_later!.

Like its name suggests, deliver_now! executes the send right away. Use this instead of the deprecated deliver method. The method which implements queues is deliver_later!.

AccountMailer.welcome(Account.first).deliver_now!
AccountMailer.welcome(Account.first).deliver_later!

Need functionality like set provides? Use these optional parameters!

AccountMailer.welcome(Account.first).deliver_later!(wait: 1.hour)
AccountMailer.welcome(Account.first).deliver_later!(wait_until: 10.hours.from_now)

Global ID

The last feature in the ActiveJob library is Global ID, which creates a URI to represent a model instance. This is helpful in situations relating to jobs. Instead of serializing a whole object or an id/class pair, just save the Global ID of the instance to the job.

At the time of job execution, the Global ID is used to locate the appropriate instance.

Let’s see this in action:

account = Account.first

account.global_id.to_s
=> "gid://sample/Account/1"

GlobalID::Locator.locate account.global_id
=> Account:0x007f9746c8a1b0

account.global_id.app
=> "sample"

account.global_id.model_name
=> "Account"

account.global_id.model_class
=> Account(id: integer, name: string, city: string, state: string, zipcode: string, latitude: integer, longitude: integer, created_at: datetime, updated_at: datetime)

account.global_id.model_id
=> "1"

Notice that a lot of data is stored in relation to the Global ID, allowing you to get a lot of meta data about the object instance.

The not so distant relative of Global ID is Signed Global ID, which is similar to Global ID except it is a signed object. The signed version can be accessed using the sgid method on the object.

account.sgid
=> SignedGlobalID:0x007f97470cac98

account.sgid.to_s
=> "BAhJIhtnaWQ6Ly9zYW1wbGUvQWNjb3VudC8xBjoGRVQ=--4b86065bf01ecf72de68ea3d34d69f5241178ea1"

The SignedGlobalID can be used to verify and retrieve a GlobalID and includes the ability to set an expiration date for the signed global ID.

ActiveJob Compatible Libraries

As of the time this article was written, ActiveJob can support the following queuing libraries:

Wrapping Up

ActiveJob is a powerful and much needed addition to the Rails framework. Now that you understand the ActiveJob library, the ActionMailer counterpart, and Global ID, you can use ActiveJob to the fullest. I’d love to hear more about how you utilized ActiveJob for your application and the things you have learned so far. Please feel free to leave a comment. Let’s have a conversation!

Frequently Asked Questions about Rails and ActiveJob

What is the main purpose of ActiveJob in Rails?

ActiveJob is a framework in Rails that standardizes the interface for creating background jobs in different queuing backends. It allows developers to write jobs and switch queuing backends with minimal changes to the code. ActiveJob provides a common interface for job runners such as Sidekiq, Resque, and Delayed Job. This means you can switch between them in your production environment with ease.

How does ActiveJob work with GlobalID in Rails?

ActiveJob integrates with GlobalID to provide support for arguments in the job data. GlobalID allows ActiveJob to serialize full Active Record objects passed as arguments to #perform. This means you can pass instances of your models directly into your job classes, without needing to worry about manually serializing and deserializing them.

How can I set up ActiveJob in my Rails application?

To set up ActiveJob in your Rails application, you need to configure your application to use a specific queue adapter. This can be done in the application configuration file. Once the queue adapter is set, you can generate jobs using the job generator. The generated job will include a perform method where you can place the code to be executed.

What are the benefits of using ActiveJob over other job runners?

ActiveJob provides a standardized interface for creating background jobs, which means you can switch between different job runners with minimal changes to your code. It also provides built-in retry and logging functionality, which can be very useful for debugging and ensuring your jobs run successfully.

How can I handle job failures in ActiveJob?

ActiveJob provides built-in mechanisms for handling job failures. You can define a rescue_from block in your job class to catch exceptions and define custom behavior for failures. Additionally, ActiveJob has a retry feature that allows you to automatically retry failed jobs.

Can I schedule jobs to run at a specific time with ActiveJob?

While ActiveJob itself does not provide scheduling capabilities, it can be used with job runners that do, such as Sidekiq or Delayed Job. These job runners allow you to schedule jobs to run at a specific time or after a certain delay.

How does ActiveJob handle job prioritization?

ActiveJob allows you to set priorities for your jobs. When enqueuing a job, you can set a priority value. The job runner will then use this value to determine the order in which jobs are processed. Higher priority jobs will be processed before lower priority jobs.

Can I use ActiveJob with any Rails version?

ActiveJob was introduced in Rails 4.2. If you’re using an older version of Rails, you’ll need to upgrade to a newer version to use ActiveJob.

How can I test my ActiveJob jobs?

Rails provides built-in test helpers for ActiveJob. You can use these helpers to test that jobs have been enqueued and that they have been performed correctly. You can also test the job logic itself by calling the perform_now method on your job class.

Can I use ActiveJob outside of a Rails application?

While ActiveJob is part of the Rails framework, it can be used outside of a Rails application. However, you’ll need to manually configure the queue adapter and load the ActiveJob framework.

Kyle SzivesKyle Szives
View Author

Kyle Szives is the co-founder of ANTLR Interactive, a Pittsburgh Web & Mobile Development company specializing in React Native Mobile App Development.

GlennG
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week