Class: ActiveSupport::CurrentAttributes

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

Overview

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.signed[: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.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeCurrentAttributes

Returns a new instance of CurrentAttributes.



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

def initialize
  @attributes = {}
end

Instance Attribute Details

#attributesObject

Returns the value of attribute attributes.



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

def attributes
  @attributes
end

Class Method Details

.attribute(*names) ⇒ Object

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



94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
# File 'lib/active_support/current_attributes/current_attributes.rb', line 94

def attribute(*names)
  generated_attribute_methods.module_eval do
    names.each do |name|
      define_method(name) do
        attributes[name.to_sym]
      end

      define_method("#{name}=") do |attribute|
        attributes[name.to_sym] = attribute
      end
    end
  end

  names.each do |name|
    define_singleton_method(name) do
      instance.public_send(name)
    end

    define_singleton_method("#{name}=") do |attribute|
      instance.public_send("#{name}=", attribute)
    end
  end
end

.clear_allObject

:nodoc:



129
130
131
132
# File 'lib/active_support/current_attributes/current_attributes.rb', line 129

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.



89
90
91
# File 'lib/active_support/current_attributes/current_attributes.rb', line 89

def instance
  current_instances[name] ||= new
end

.reset_allObject

:nodoc:



125
126
127
# File 'lib/active_support/current_attributes/current_attributes.rb', line 125

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

.resets(&block) ⇒ Object

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



119
120
121
# File 'lib/active_support/current_attributes/current_attributes.rb', line 119

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

Instance Method Details

#resetObject

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



178
179
180
181
182
# File 'lib/active_support/current_attributes/current_attributes.rb', line 178

def reset
  run_callbacks :reset do
    self.attributes = {}
  end
end

#set(set_attributes) ⇒ 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


169
170
171
172
173
174
175
# File 'lib/active_support/current_attributes/current_attributes.rb', line 169

def set(set_attributes)
  old_attributes = compute_attributes(set_attributes.keys)
  assign_attributes(set_attributes)
  yield
ensure
  assign_attributes(old_attributes)
end