Managing context like the current user, store, client, or request ID across controllers, models, jobs, and services in a Rails app has always been a little messy. Before Rails 5.2, you mightโ€™ve reached for Thread.current, class_attribute, or even @@class_variables to store this kind of state. But these are not thread-safe and can cause hard-to-debug issues in concurrent environments.

Rails 5.2 introduced a clean solution: ActiveSupport::CurrentAttributes.

This article covers what it is, why it exists, how it works internally, how to use it safely across web and background jobs, and how to test it properly.


๐Ÿ“œ A Quick History

ActiveSupport::CurrentAttributes was introduced by DHH in Rails 5.2 to simplify access to per-request or per-job context in a thread-safe way. It replaced older, hackier methods like:

1
2
Thread.current[:current_user] = user
ApplicationRecord.class_attribute :current_client

These approaches were shared across threads โ€” fine in development, dangerous in production.

CurrentAttributes changed that by giving you a dedicated, Rails-friendly API for storing context thatโ€™s isolated per request/job.


๐Ÿงฉ What Is CurrentAttributes?

CurrentAttributes is a base class that lets you define request- or job-scoped attributes (like user, client, or store) and access them globally during execution.

1
2
3
4
5
6
7
8
9
# app/models/current.rb
class Current < ActiveSupport::CurrentAttributes
  attribute :user, :client, :store

  def user=(user)
    super
    Time.zone = user.time_zone if user.respond_to?(:time_zone)
  end
end

Set it early in the request or job:

1
2
3
4
5
6
7
8
9
class ApplicationController < ActionController::Base
  before_action :set_current_context

  def set_current_context
    Current.user = current_user
    Current.store = Store.find_by!(subdomain: request.subdomain)
    Current.client = request.headers["X-Client-ID"]
  end
end

Now in any service, model, or job:

1
AuditLog.create!(user: Current.user, store: Current.store)

๐Ÿšซ Why Not Use class_attribute or Thread.current?

Because:

  • โŒ class_attribute and @@variables are global across all threads.
  • โŒ Thread.current is unstructured and doesnโ€™t reset automatically.
  • โŒ These approaches are hard to test and can lead to subtle concurrency bugs.

๐Ÿ›  How Does It Work Internally?

Storage

Each attribute is stored using Thread.current or Fiber.current (depending on your Ruby version), isolated per thread or fiber.

1
2
3
4
5
6
7
def current_attributes
  storage[self.name] ||= {}
end

def storage
  ActiveSupport::IsolatedExecutionState[:current_attributes] ||= {}
end

Resetting

Rails automatically resets context after each request using ActionDispatch::Executor:

1
Current.reset_all

You can (and should) call this manually at the end of background jobs.


โœ… Using It in Sidekiq

1
2
3
4
5
6
7
8
9
class MyJob
  include Sidekiq::Job

  def perform(client_id)
    Current.set(client: Client.find(client_id)) do
      ImportantService.call
    end
  end
end

This is thread-safe: each Sidekiq job runs in its own thread, and CurrentAttributes isolates state per job.


โœ… Using It in Tests

Manual setup:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
class OrdersControllerTest < ActionDispatch::IntegrationTest
  setup do
    @user = users(:alice)
    @store = stores(:default)

    Current.user = @user
    Current.store = @store
  end

  teardown do
    Current.reset_all
  end

  test "should create order" do
    post orders_url, params: { order: { name: "Test Order" } }
    assert_equal @user, Order.last.user
  end
end

Shared helper:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# test/test_helper.rb
module CurrentHelper
  def with_current(user: nil, store: nil, client: nil)
    Current.user = user if user
    Current.store = store if store
    Current.client = client if client
    yield
  ensure
    Current.reset_all
  end
end

Now in tests:

1
2
3
4
5
test "with context" do
  with_current(user: @user, store: @store) do
    post orders_url, params: { order: { name: "Test" } }
  end
end

โœ… Best Practices

Do โœ… Donโ€™t โŒ
Set Current.* early Load values lazily from inside Current
Use Current.set {} blocks Store heavy or unrelated state
Reset Current after each job/test Use class_attribute for request context

๐Ÿง  Summary

  • CurrentAttributes is a clean, thread-safe, per-request container.
  • Use it to store context like user, store, and client.
  • Set it early in controllers or jobs.
  • Avoid putting logic inside it.
  • Use reset_all manually in jobs and tests.

Itโ€™s not just a substitute for class_attribute or Thread.current โ€” itโ€™s the right tool for request-scoped state in modern Rails apps.