Ruby
Article

New Rails Shiny: ActiveJob

By Kyle Szives

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!

Free Guide:

7 Habits of Successful CTOs

"What makes a great CTO?" Engineering skills? Business savvy? An innate tendency to channel a mythical creature (ahem, unicorn)? All of the above? Discover the top traits of the most successful CTOs in this free guide.

No Reader comments

Recommended
Sponsors
Because We Like You
Free Ebooks!

Grab SitePoint's top 10 web dev and design ebooks, completely free!

Get the latest in Ruby, once a week, for free.