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
module SetCurrentContext
extend ActiveSupport::Concern
included do
before_action :set_current_attributes
end
private
def set_current_attributes
Current.request_id = request.request_id
Current.ip_address = request.remote_ip
Current.user = current_user
Current.tenant = resolve_tenant
end
def resolve_tenant
subdomain = request.subdomains.first
return nil if subdomain.blank?
Tenant.find_by(slug: subdomain)
end
end
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
included_auditable = ->(klass) do
klass.after_create :record_audit_entry
end
private
def record_audit_entry
return unless Current.user.present?
AuditLog.create!(
actor_id: Current.user.id,
tenant_id: Current.tenant_id,
request_id: Current.request_id,
ip_address: Current.ip_address,
auditable_type: self.class.name,
auditable_id: id,
action: "create"
)
end
end
class ExportReportJob < ApplicationJob
queue_as :exports
def self.enqueue_with_context(report)
perform_later(
report_id: report.id,
tenant_id: Current.tenant_id,
user_id: Current.user&.id
)
end
def perform(report_id:, tenant_id:, user_id:)
tenant = Tenant.find(tenant_id)
user = user_id && User.find(user_id)
Current.set(tenant: tenant, user: user, request_id: "job:#{job_id}") do
report = tenant.reports.find(report_id)
ReportBuilder.new(report).generate!
end
end
end
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
class CommentsController < ApplicationController
before_action :set_post
def create
@comment = @post.comments.build(comment_params)
System test: asserting Turbo Stream responses
class Post < ApplicationRecord
belongs_to :author, class_name: 'User'
has_many :comments, dependent: :destroy
scope :published, -> { where.not(published_at: nil).where('published_at <= ?', Time.current) }
scope :draft, -> { where(published_at: nil) }
ActiveRecord scopes for reusable query logic
import os
import stat
for root, _dirs, files in os.walk('/etc'):
for name in files:
path = os.path.join(root, name)
Python security audit script for exposed risky filesystem state
module Api
module V1
class UsersController < BaseController
def show
user = User.includes(:profile).find(params[:id])
ETags for conditional requests and caching
class PostsController < ApplicationController
def index
@posts = Post.includes(:author)
.order(created_at: :desc)
.page(params[:page])
.per(10)
Turbo Frames: infinite scroll with lazy-loading frame
require "csv"
class PeopleCsvStream
include Enumerable
HEADERS = %w[id full_name email signed_up_at plan].freeze
Resilient CSV Export as a Streamed Response
Share this code
Here's the card — post it anywhere.