Class: ActiveSupport::CurrentAttributes
| Relationships & Source Files | |
| Namespace Children | |
|
Modules:
| |
| Super Chains via Extension / Inclusion / Inheritance | |
|
Class Chain:
self,
DescendantsTracker
|
|
|
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: .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.account..create()
end
end
class Message < ApplicationRecord
belongs_to :creator, default: -> { Current.user }
after_create { || Event.create(record: ) }
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
# File 'activesupport/lib/active_support/current_attributes.rb', line 250
Declaring an attribute by one of these names would shadow the methods
CurrentAttributesitself relies on. Computed at the very end of the class body so that all the methods defined above are captured.(methods(false) + private_methods(false) + instance_methods(false) + private_instance_methods(false)).uniq.sort.freeze
-
NOT_SET =
Internal use only
# File 'activesupport/lib/active_support/current_attributes.rb', line 101Object.new.freeze
Callbacks - Included
Callbacks - Attributes & Methods
- .__callbacks rw
- #__callbacks readonly
Class Attribute Summary
Class Method Summary
-
.after_reset(*methods, &block)
Alias for .resets.
-
.attribute(*names, default: NOT_SET)
Declares one or more attributes that will be given both class and instance accessor methods.
-
.before_reset(*methods, &block)
Calls this callback before #reset is called on the instance.
-
.instance
Returns singleton instance for this class in this thread.
- .new ⇒ CurrentAttributes constructor
-
.resets(*methods, &block)
(also: .after_reset)
Calls this callback after #reset is called on the instance.
- .current_instances private
- .current_instances_key private
- .generated_attribute_methods private
- .method_added(name) private
- .method_missing(name) private
- .respond_to_missing?(name, _) ⇒ Boolean private
- .clear_all Internal use only
DescendantsTracker - self
Instance Attribute Summary
- #attributes rw
- #attributes=(value) rw
- #defaults readonly
- #defaults? ⇒ Boolean readonly
-
#reset
readonly
Reset all attributes.
-
#set(attributes, &block)
readonly
Expose one or more attributes within a block.
Instance Method Summary
- #resolve_defaults private
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
# 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 77
class_attribute :__callbacks, instance_writer: false, instance_predicate: false, default: {}
.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.
# File 'activesupport/lib/active_support/current_attributes.rb', line 155
alias_method :after_reset, :resets
.attribute(*names, default: NOT_SET)
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.
# File 'activesupport/lib/active_support/current_attributes.rb', line 117
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 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 = 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.
# File 'activesupport/lib/active_support/current_attributes.rb', line 147
def before_reset(*methods, &block) set_callback :reset, :before, *methods, &block end
.clear_all
# File 'activesupport/lib/active_support/current_attributes.rb', line 159
def clear_all # :nodoc: if instances = current_instances 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 ].instance
Returns singleton instance for this class in this thread. If none exists, one is created.
# File 'activesupport/lib/active_support/current_attributes.rb', line 105
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
# 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)
# 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 77
class_attribute :__callbacks, instance_writer: false, instance_predicate: false, default: {}
#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.
#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