Class: ActiveSupport::CurrentAttributes

Inherits:
Object
  • Object
show all
Includes:
Callbacks
Defined in:
activesupport/lib/active_support/current_attributes.rb

Overview

Current Attributes

Abstract super class that provides a thread-isolated attributes singleton, which resets automatically before and after each request. This allows you to keep all the per-request attributes easily available to the whole system.

The following full app-like example demonstrates how to use a Current class to facilitate easy access to the global, per-request attributes without passing them deeply around everywhere:

# app/models/current.rb
class Current < ActiveSupport::CurrentAttributes
attribute :account, :user
attribute :request_id, :user_agent, :ip_address

resets { Time.zone = nil }

def user=(user)
  super
  self. = user.
  Time.zone    = user.time_zone
end
end

# app/controllers/concerns/authentication.rb
module Authentication
extend ActiveSupport::Concern

included do
  before_action :authenticate
end

private
  def authenticate
    if authenticated_user = User.find_by(id: cookies.encrypted[:user_id])
      Current.user = authenticated_user
    else
      redirect_to new_session_url
    end
  end
end

# app/controllers/concerns/set_current_request_details.rb
module SetCurrentRequestDetails
extend ActiveSupport::Concern

included do
  before_action do
    Current.request_id = request.uuid
    Current.user_agent = request.user_agent
    Current.ip_address = request.ip
  end
end
end

class ApplicationController < ActionController::Base
include Authentication
include SetCurrentRequestDetails
end

class MessagesController < ApplicationController
def create
  Current..messages.create(message_params)
end
end

class Message < ApplicationRecord
belongs_to :creator, default: -> { Current.user }
after_create { |message| Event.create(record: message) }
end

class Event < ApplicationRecord
before_create do
  self.request_id = Current.request_id
  self.user_agent = Current.user_agent
  self.ip_address = Current.ip_address
end
end

A word of caution: It's easy to overdo a global singleton like Current and tangle your model as a result. Current should only be used for a few, top-level globals, like account, user, and request details. The attributes stuck in Current should be used by more or less all actions on all requests. If you start sticking controller-specific attributes in there, you're going to create a mess.

Direct Known Subclasses

ActiveStorage::Current

Defined Under Namespace

Modules: TestHelper

Constant Summary collapse

INVALID_ATTRIBUTE_NAMES =

:nodoc:

[:set, :reset, :resets, :instance, :before_reset, :after_reset, :reset_all, :clear_all]
NOT_SET =

:nodoc:

Object.new.freeze

Constants included from Callbacks

ActiveSupport::Callbacks::CALLBACK_FILTER_TYPES

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Callbacks

#run_callbacks

Methods included from Concern

#append_features, #class_methods, extended, #included, #prepend_features, #prepended

Constructor Details

#initializeCurrentAttributes

Returns a new instance of CurrentAttributes.



199
200
201
# File 'activesupport/lib/active_support/current_attributes.rb', line 199

def initialize
  @attributes = resolve_defaults
end

Instance Attribute Details

#attributesObject

Returns the value of attribute attributes.



197
198
199
# File 'activesupport/lib/active_support/current_attributes.rb', line 197

def attributes
  @attributes
end

Class Method Details

.attribute(*names, default: NOT_SET) ⇒ Object

Declares one or more attributes that will be given both class and instance accessor methods.

Options

  • :default - The default value for the attributes. If the value is a proc or lambda, it will be called whenever an instance is constructed. Otherwise, the value will be duplicated with #dup. Default values are re-assigned when the attributes are reset.


114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# File 'activesupport/lib/active_support/current_attributes.rb', line 114

def attribute(*names, default: NOT_SET)
  invalid_attribute_names = names.map(&:to_sym) & INVALID_ATTRIBUTE_NAMES
  if invalid_attribute_names.any?
    raise ArgumentError, "Restricted attribute names: #{invalid_attribute_names.join(", ")}"
  end

  ActiveSupport::CodeGenerator.batch(generated_attribute_methods, __FILE__, __LINE__) do |owner|
    names.each do |name|
      owner.define_cached_method(name, namespace: :current_attributes) do |batch|
        batch <<
          "def #{name}" <<
          "attributes[:#{name}]" <<
          "end"
      end
      owner.define_cached_method("#{name}=", namespace: :current_attributes) do |batch|
        batch <<
          "def #{name}=(value)" <<
          "attributes[:#{name}] = value" <<
          "end"
      end
    end
  end

  Delegation.generate(singleton_class, names, to: :instance, nilable: false, signature: "")
  Delegation.generate(singleton_class, names.map { |n| "#{n}=" }, to: :instance, nilable: false, signature: "value")

  self.defaults = defaults.merge(names.index_with { default })
end

.before_reset(*methods, &block) ⇒ Object

Calls this callback before #reset is called on the instance. Used for resetting external collaborators that depend on current values.



144
145
146
# File 'activesupport/lib/active_support/current_attributes.rb', line 144

def before_reset(*methods, &block)
  set_callback :reset, :before, *methods, &block
end

.clear_allObject

:nodoc:



160
161
162
163
# File 'activesupport/lib/active_support/current_attributes.rb', line 160

def clear_all # :nodoc:
  reset_all
  current_instances.clear
end

.instanceObject

Returns singleton instance for this class in this thread. If none exists, one is created.



102
103
104
# File 'activesupport/lib/active_support/current_attributes.rb', line 102

def instance
  current_instances[current_instances_key] ||= new
end

.reset_allObject

:nodoc:



156
157
158
# File 'activesupport/lib/active_support/current_attributes.rb', line 156

def reset_all # :nodoc:
  current_instances.each_value(&:reset)
end

.resets(*methods, &block) ⇒ Object Also known as: after_reset

Calls this callback after #reset is called on the instance. Used for resetting external collaborators, like Time.zone.



149
150
151
# File 'activesupport/lib/active_support/current_attributes.rb', line 149

def resets(*methods, &block)
  set_callback :reset, :after, *methods, &block
end

Instance Method Details

#resetObject

Reset all attributes. Should be called before and after actions, when used as a per-request singleton.



218
219
220
221
222
# File 'activesupport/lib/active_support/current_attributes.rb', line 218

def reset
  run_callbacks :reset do
    self.attributes = resolve_defaults
  end
end

#set(attributes, &block) ⇒ Object

Expose one or more attributes within a block. Old values are returned after the block concludes. Example demonstrating the common use of needing to set Current attributes outside the request-cycle:

class Chat::PublicationJob < ApplicationJob
def perform(attributes, room_number, creator)
  Current.set(person: creator) do
    Chat::Publisher.publish(attributes: attributes, room_number: room_number)
  end
end
end


213
214
215
# File 'activesupport/lib/active_support/current_attributes.rb', line 213

def set(attributes, &block)
  with(**attributes, &block)
end