123456789_123456789_123456789_123456789_123456789_

Class: ActiveSupport::CurrentAttributes

Relationships & Source Files
Namespace Children
Modules:
Super Chains via Extension / Inclusion / Inheritance
Class Chain:
Instance Chain:
self, Callbacks
Inherits: Object
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 < <code>CurrentAttributes</code>
  attribute :account, :user
  attribute :request_id, :user_agent, :ip_address

  resets { Time.zone = nil }

  def user=(user)
    super
    self.account = user.account
    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.

Constant Summary

  • INVALID_ATTRIBUTE_NAMES = Internal use only

    Declaring an attribute by one of these names would shadow the methods CurrentAttributes itself relies on. Computed at the very end of the class body so that all the methods defined above are captured.

    # File 'activesupport/lib/active_support/current_attributes.rb', line 248
    (methods(false) + private_methods(false) + instance_methods(false) + private_instance_methods(false)).uniq.sort.freeze

Callbacks - Included

CALLBACK_FILTER_TYPES

Callbacks - Attributes & Methods

Class Attribute Summary

Class Method Summary

DescendantsTracker - self

Instance Attribute Summary

Instance Method Summary

Callbacks - Included

#run_callbacks

Runs the callbacks for the given event.

#halted_callback_hook

A hook invoked every time a before callback is halted.

Constructor Details

.new ⇒ CurrentAttributes

[ GitHub ]

  
# File 'activesupport/lib/active_support/current_attributes.rb', line 207

def initialize
  @attributes = resolve_defaults
end

Class Attribute Details

.__callbacks (rw)

[ GitHub ]

  
# File 'activesupport/lib/active_support/callbacks.rb', line 72

class_attribute :__callbacks, instance_writer: false, instance_predicate: false, default: {}.freeze

.defaults (rw)

[ GitHub ]

  
# File 'activesupport/lib/active_support/current_attributes.rb', line 203

class_attribute :defaults, instance_writer: false, default: {}.freeze

.defaults? ⇒ Boolean (rw)

[ GitHub ]

  
# File 'activesupport/lib/active_support/current_attributes.rb', line 203

class_attribute :defaults, instance_writer: false, default: {}.freeze

Class Method Details

.after_reset(*methods, &block)

Alias for .resets.

[ GitHub ]

  
# File 'activesupport/lib/active_support/current_attributes.rb', line 155

alias_method :after_reset, :resets

.attribute(*names, default: (no_default = true))

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.
[ GitHub ]

  
# File 'activesupport/lib/active_support/current_attributes.rb', line 115

def attribute(*names, default: (no_default = true))
  names = names.map(&:to_sym)

  invalid_attribute_names = names & INVALID_ATTRIBUTE_NAMES
  if invalid_attribute_names.any?
    raise ArgumentError, "Restricted attribute names: #{invalid_attribute_names.join(", ")}"
  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")

  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

  self.defaults = no_default ? defaults.except(*names) : defaults.merge(names.index_with { default })
end

.before_reset(*methods, &block)

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

[ GitHub ]

  
# File 'activesupport/lib/active_support/current_attributes.rb', line 147

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

.clear_all

This method is for internal use only.
[ GitHub ]

  
# File 'activesupport/lib/active_support/current_attributes.rb', line 159

def clear_all # :nodoc:
  if (instances = current_instances) && !instances.empty?
    instances.values.each(&:reset)
    instances.clear
  end
end

.current_instances (private)

[ GitHub ]

  
# File 'activesupport/lib/active_support/current_attributes.rb', line 171

def current_instances
  ExecutionContext.current_attributes_instances
end

.current_instances_key (private)

[ GitHub ]

  
# File 'activesupport/lib/active_support/current_attributes.rb', line 175

def current_instances_key
  @current_instances_key ||= name.to_sym
end

.generated_attribute_methods (private)

[ GitHub ]

  
# File 'activesupport/lib/active_support/current_attributes.rb', line 167

def generated_attribute_methods
  @generated_attribute_methods ||= Module.new.tap { |mod| include mod }
end

.instance

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

[ GitHub ]

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

def instance
  current_instances[current_instances_key] ||= new
end

.method_added(name) (private)

[ GitHub ]

  
# File 'activesupport/lib/active_support/current_attributes.rb', line 187

def method_added(name)
  super

  # We try to generate instance delegators early to not rely on method_missing.
  return if name == :initialize

  # If the added method isn't public, we don't delegate it.
  return unless public_method_defined?(name)

  # If we already have a class method by that name, we don't override it.
  return if singleton_class.method_defined?(name) || singleton_class.private_method_defined?(name)

  Delegation.generate(singleton_class, [name], to: :instance, as: self, nilable: false)
end

.method_missing(name) (private)

[ GitHub ]

  
# File 'activesupport/lib/active_support/current_attributes.rb', line 179

def method_missing(name, ...)
  instance.public_send(name, ...)
end

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

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

[ GitHub ]

  
# File 'activesupport/lib/active_support/current_attributes.rb', line 152

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

.respond_to_missing?(name, _) ⇒ Boolean (private)

[ GitHub ]

  
# File 'activesupport/lib/active_support/current_attributes.rb', line 183

def respond_to_missing?(name, _)
  instance.respond_to?(name) || super
end

Instance Attribute Details

#__callbacks (readonly)

[ GitHub ]

  
# File 'activesupport/lib/active_support/callbacks.rb', line 72

class_attribute :__callbacks, instance_writer: false, instance_predicate: false, default: {}.freeze

#attributes (rw)

[ GitHub ]

  
# File 'activesupport/lib/active_support/current_attributes.rb', line 211

def attributes
  @attributes.dup
end

#attributes=(value) (rw)

[ GitHub ]

  
# File 'activesupport/lib/active_support/current_attributes.rb', line 205

attr_writer :attributes

#defaults (readonly)

[ GitHub ]

  
# File 'activesupport/lib/active_support/current_attributes.rb', line 203

class_attribute :defaults, instance_writer: false, default: {}.freeze

#defaults? ⇒ Boolean (readonly)

[ GitHub ]

  
# File 'activesupport/lib/active_support/current_attributes.rb', line 203

class_attribute :defaults, instance_writer: false, default: {}.freeze

#reset (readonly)

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

[ GitHub ]

  
# File 'activesupport/lib/active_support/current_attributes.rb', line 232

delegate :set, :reset, to: :instance

#set(attributes, &block) (readonly)

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
[ GitHub ]

  
# File 'activesupport/lib/active_support/current_attributes.rb', line 227

delegate :set, :reset, to: :instance

Instance Method Details

#resolve_defaults (private)

[ GitHub ]

  
# File 'activesupport/lib/active_support/current_attributes.rb', line 239

def resolve_defaults
  defaults.transform_values do |value|
    Proc === value ? value.call : value.dup
  end
end