ActiveSupport::PerThreadRegistry vs thread_mattr_accessor
在看ActiveRecord.current_scope时,发现ActiveSupport::PerThreadRegistry这种东西
PerThreadRegistry用例
源码已提及它是不建议使用的
# NOTE: This approach has been deprecated for end-user code in favor of thread_mattr_accessor and friends.
# Please use that approach instead.
不过5.0.2还在用
module ActiveRecord
module Scoping
class ScopeRegistry # :nodoc:
extend ActiveSupport::PerThreadRegistry
VALID_SCOPE_TYPES = [:current_scope, :ignore_default_scope]
def initialize
@registry = Hash.new { |hash, key| hash[key] = {} }
end
# Obtains the value for a given +scope_type+ and +model+.
def value_for(scope_type, model)
raise_invalid_scope_type!(scope_type)
klass = model
base = model.base_class
while klass <= base
value = @registry[scope_type][klass.name]
return value if value
klass = klass.superclass
end
end
# Sets the +value+ for a given +scope_type+ and +model+.
def set_value_for(scope_type, model, value)
raise_invalid_scope_type!(scope_type)
@registry[scope_type][model.name] = value
end
private
def raise_invalid_scope_type!(scope_type)
if !VALID_SCOPE_TYPES.include?(scope_type)
raise ArgumentError, "Invalid scope type '#{scope_type}' sent to the registry. Scope types must be included in VALID_SCOPE_TYPES"
end
end
end
end
end
PerThreadRegistry源码
require 'active_support/core_ext/module/delegation'
module ActiveSupport
module PerThreadRegistry
# extend此module的class A会被设置一个
# 名为@per_thread_registry_key, 值为A的类实例变量
def self.extended(object)
object.instance_variable_set '@per_thread_registry_key', object.name.freeze
end
# 每次以类方法的形式调用instance,都会在ThredLocal获取相同的实例
def instance
Thread.current[@per_thread_registry_key] ||= new
end
# 但其实一般不会直接使用instance,而是以类方法的形式调用实例方法
# 当然一开始是不存在的,需要委托到ThredLocal上(动态定义,以便下次直接调用)
# 用例如上所示
protected
def method_missing(name, *args, &block) # :nodoc:
# Caches the method definition as a singleton method of the receiver.
#
# By letting #delegate handle it, we avoid an enclosure that'll capture args.
singleton_class.delegate name, to: :instance
send(name, *args, &block)
end
end
end
thread_mattr_accessor源码
既然说到thread_mattr_accessor,那也看看它
可以发现,其实它跟ActiveSupport::PerThreadRegistry的用途是不太一样的。
extend ActiveSupport::PerThreadRegistry的类,它的实例本身应具备功能
而thread_mattr_reader仅仅使module/class变成一个有名字的ThreadLocal接口,它本身具不具备功能是无关紧要的
class module
def thread_mattr_reader(*syms) # :nodoc:
options = syms.extract_options!
syms.each do |sym|
raise NameError.new("invalid attribute name: #{sym}") unless sym =~ /^[_A-Za-z]\w*$/
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
def self.#{sym}
Thread.current["attr_"+ name + "_#{sym}"]
end
EOS
unless options[:instance_reader] == false || options[:instance_accessor] == false
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
def #{sym}
Thread.current["attr_"+ self.class.name + "_#{sym}"]
end
EOS
end
end
end
def thread_mattr_writer(*syms) # :nodoc:
options = syms.extract_options!
syms.each do |sym|
raise NameError.new("invalid attribute name: #{sym}") unless sym =~ /^[_A-Za-z]\w*$/
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
def self.#{sym}=(obj)
Thread.current["attr_"+ name + "_#{sym}"] = obj
end
EOS
unless options[:instance_writer] == false || options[:instance_accessor] == false
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
def #{sym}=(obj)
Thread.current["attr_"+ self.class.name + "_#{sym}"] = obj
end
EOS
end
end
end
def thread_mattr_accessor(*syms, &blk)
thread_mattr_reader(*syms, &blk)
thread_mattr_writer(*syms, &blk)
end
end
thread_mattr_reader用例
当然,这个Worker是具备自身功能的,但它的连接功能是来自参数connection
module ActionCable
module Server
class Worker
include ActiveSupport::Callbacks
thread_mattr_accessor :connection
def work(connection)
self.connection = connection
run_callbacks :work do
yield
end
ensure
self.connection = nil
end