在看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