Skip to content
← All posts
5 min read Dawid Skłodowski

Rails 4.2: ActiveJob, Adequate Record, and foreign keys

The Rails 4.2 release candidate is out. A look at the three changes that matter most — a unified background-job API, a quietly large performance gain in ActiveRecord, and foreign keys finally landing in migrations.

The Rails 4.2 release candidate has landed, with the final not far behind. It is a focused release — three headline features and a scattering of smaller ones — but each headline addresses something developers have worked around for years. We have been running the betas, and here is what 4.2 actually changes for the way you build Rails apps.

ActiveJob: one API for background work

Every non-trivial Rails app pushes slow work into the background — sending email, processing uploads, calling third-party APIs. The trouble has always been that the queueing libraries are incompatible: Sidekiq, Resque, Delayed Job, and the rest each have their own way to define and enqueue a job. Switch backends and you rewrite all your job code; write a gem that needs background work and you cannot know which backend the host app uses.

ActiveJob fixes this by being the adapter layer Rails was missing — a single, backend-agnostic API for declaring and enqueuing jobs:

class ThumbnailJob < ApplicationJob
  queue_as :default

  def perform(upload)
    upload.generate_thumbnail!
  end
end

ThumbnailJob.perform_later(upload)     # enqueue
ThumbnailJob.set(wait: 1.hour).perform_later(upload)   # enqueue, delayed

You write your jobs once against ActiveJob, and a one-line config picks the actual queue backend:

config.active_job.queue_adapter = :sidekiq

Swap :sidekiq for :resque or :delayed_job and not a line of job code changes. Two things make this more than a thin wrapper. First, GlobalID: ActiveJob serialises ActiveRecord objects by a global identifier and reloads them in the worker, so you can pass a model to perform_later and receive the model (not a raw id) in perform — a small convenience that removes a lot of boilerplate. Second, Action Mailer integration: deliver_later now sends email through ActiveJob, so “send this email in the background” is finally a framework-level, backend-agnostic one-liner instead of per-backend glue.

The honest caveat: ActiveJob is an abstraction over backends that still differ in real ways (retries, dead-job handling, concurrency). It standardises the interface, not every behaviour, so you still choose Sidekiq or Resque for their actual characteristics — you just stop coupling your job code to that choice.

Adequate Record: free performance

The most cheekily-named feature is also the one you do nothing to get. “Adequate Record” is a substantial internal optimisation of ActiveRecord that caches the computed SQL and prepared statements for common query patterns. The first time you run a particular shape of query, AR does the work of building it; subsequent identical-shape queries reuse the cached plan and skip a chunk of that overhead.

# the SECOND and later calls of this shape are noticeably faster,
# because the generated SQL / prepared statement is cached
Post.find(1)
user.posts.where(published: true).to_a

The reported speed-ups on common operations like find and association loading are roughly a 2× improvement, and you get them by upgrading — no code changes, no new API. For read-heavy apps that issue the same query shapes millions of times, that is a meaningful, free win. It is exactly the kind of unglamorous core work that makes upgrading Rails worth it even when the headline feature list looks short.

Foreign keys in migrations, at last

This one is overdue and very welcome. For years, ActiveRecord let you declare associations in your models while leaving the database with no actual foreign key constraint — so referential integrity was enforced (at best) by application code and entirely absent at the database level. Delete a record another row referenced and the database would shrug; you were left with orphans and dangling references that some validation was supposed to prevent but, under concurrency or a stray script, did not.

4.2 brings foreign keys into the migration DSL as first-class citizens:

class AddForeignKeys < ActiveRecord::Migration
  def change
    add_foreign_key :comments, :posts
    add_foreign_key :comments, :users, on_delete: :cascade
  end
end

# or inline when creating the table:
create_table :comments do |t|
  t.references :post, foreign_key: true
  t.references :user, foreign_key: true
end

Now the database guarantees that a comment cannot reference a non-existent post, and on_delete: :cascade lets you push deletion rules down to where they belong. This is the database doing the job it is uniquely good at — enforcing invariants that must hold no matter what code, script, or race tries to violate them. We have long added these by hand in raw SQL migrations; having them in the DSL means they are now the easy, default path, and more apps will get the integrity they should have had all along. (Postgres and MySQL get this; SQLite’s support is limited, as ever.)

The smaller additions

A few more worth noting: the Web Console gives you an interactive irb session right in the browser on an error page (and via console in a view) — a genuinely useful debugging aid, though one to keep firmly out of production. render gains the ability to render templates from outside the controller’s own view paths. And there is the usual round of ActiveSupport refinements and deprecation cleanups as the framework keeps tidying itself.

Verdict

4.2 is a high-value, low-friction upgrade. ActiveJob is the standardisation background processing always needed and immediately makes your job code more portable and your mailers easier; Adequate Record hands you a real performance improvement for the cost of bundle update; and foreign keys close a correctness gap that has quietly bitten Rails apps for a decade. None of it requires you to rewrite anything — adopt ActiveJob as you touch your jobs, add foreign keys in your next migration, and enjoy the speed-up for free. This is Rails maturing in the best way: not chasing novelty, but fixing the things that experienced teams had learned to route around.