ruby 84 lines · 4 tabs

CurrentAttributes for Request-Scoped Context

Shared by codesnips Jan 2026
4 tabs
class Current < ActiveSupport::CurrentAttributes
  attribute :user, :tenant, :request_id, :ip_address

  def user_display
    user&.email || "system"
  end

  def tenant_id
    tenant&.id
  end

  def self.with_context(user:, tenant:, request_id: nil, ip_address: nil)
    set(user: user, tenant: tenant, request_id: request_id, ip_address: ip_address) do
      yield
    end
  end
end
4 files · ruby Explain with highlit

Rails' ActiveSupport::CurrentAttributes provides a per-request singleton whose attributes are reset automatically at the end of every request and every job. This makes it a clean place to stash ambient context — the current user, tenant, request id, IP — that many layers need but nobody wants to thread through every method call. The pattern solves the temptation to pass a current_user argument down through models, services, and mailers, while avoiding the classic danger of leaking state between requests when threads are reused.

In Current model, the subclass declares attribute slots and a small set helper that wraps Rails' own Current.set to establish context for a block. Two derived reader helpers, user_display and tenant_id, keep view and log code from repeatedly guarding against nil. Because CurrentAttributes instances are stored in fiber/thread-local storage and cleared by the framework, code must never memoize Current.user at boot time or in a class variable — it is only valid for the duration of one request.

SetCurrentContext controller concern runs a before_action to populate Current from the authenticated session and request metadata. It sets Current.request_id from request.request_id, the actor, and the tenant resolved from the subdomain. Doing this once in a base controller means every model and service invoked during the request sees consistent context without extra plumbing.

ApplicationRecord auditing shows the payoff: an after_create callback writes an AuditLog row stamped with Current.user and Current.request_id, so audit trails are automatically correlated to the actor and the originating request. The guard on Current.user.present? handles background contexts where no user exists, such as console sessions or system jobs.

TenantContext job wrapper demonstrates that context does not cross into background jobs for free. The enqueue path captures tenant_id and user_id as plain serializable arguments, then the job re-establishes Current inside perform using Current.set. This explicit hand-off is essential — relying on the web request's Current inside an async worker would find it already reset. The trade-off of CurrentAttributes is that it is global mutable state; it is best confined to genuinely cross-cutting concerns like tenancy, locale, and auditing rather than becoming a dumping ground for arbitrary data.


Related snips

Share this code

Here's the card — post it anywhere.

CurrentAttributes for Request-Scoped Context — share card
Link copied