# frozen_string_literal: true # Extends the module object with class/module and instance accessors for # class/module attributes, just like the native attr* accessors for instance # attributes, but does so on a per-thread basis. # # So the values are scoped within the Thread.current space under the class name # of the module. class Module # Defines a per-thread class attribute and creates class and instance reader methods. # The underlying per-thread class variable is set to +nil+, if it is not previously defined. # # module Current # thread_mattr_reader :user # end # # Current.user # => nil # Thread.current[:attr_Current_user] = "DHH" # Current.user # => "DHH" # # The attribute name must be a valid method name in Ruby. # # module Foo # thread_mattr_reader :"1_Badname" # end # # => NameError: invalid attribute name: 1_Badname # # To omit the instance reader method, pass # instance_reader: false or instance_accessor: false. # # class Current # thread_mattr_reader :user, instance_reader: false # end # # Current.new.user # => NoMethodError def thread_mattr_reader(*syms, instance_reader: true, instance_accessor: true, default: nil) # :nodoc: syms.each do |sym| raise NameError.new("invalid attribute name: #{sym}") unless /^[_A-Za-z]\w*$/.match?(sym) # The following generated method concatenates `name` because we want it # to work with inheritance via polymorphism. class_eval(<<-EOS, __FILE__, __LINE__ + 1) def self.#{sym} Thread.current["attr_" + name + "_#{sym}"] end EOS if instance_reader && instance_accessor class_eval(<<-EOS, __FILE__, __LINE__ + 1) def #{sym} self.class.#{sym} end EOS end default_val = (block_given? && default.nil?) ? yield : default Thread.current["attr_" + name + "_#{sym}"] = default_val unless default_val.nil? end end alias :thread_cattr_reader :thread_mattr_reader # Defines a per-thread class attribute and creates a class and instance writer methods to # allow assignment to the attribute. # # module Current # thread_mattr_writer :user # end # # Current.user = "DHH" # Thread.current[:attr_Current_user] # => "DHH" # # To omit the instance writer method, pass # instance_writer: false or instance_accessor: false. # # class Current # thread_mattr_writer :user, instance_writer: false # end # # Current.new.user = "DHH" # => NoMethodError def thread_mattr_writer(*syms, instance_writer: true, instance_accessor: true, default: nil) # :nodoc: syms.each do |sym| raise NameError.new("invalid attribute name: #{sym}") unless /^[_A-Za-z]\w*$/.match?(sym) # The following generated method concatenates `name` because we want it # to work with inheritance via polymorphism. class_eval(<<-EOS, __FILE__, __LINE__ + 1) def self.#{sym}=(obj) Thread.current["attr_" + name + "_#{sym}"] = obj end EOS if instance_writer && instance_accessor class_eval(<<-EOS, __FILE__, __LINE__ + 1) def #{sym}=(obj) self.class.#{sym} = obj end EOS end default_val = (block_given? && default.nil?) ? yield : default Thread.current["attr_" + name + "_#{sym}"] = default_val unless default_val.nil? end end alias :thread_cattr_writer :thread_mattr_writer # Defines both class and instance accessors for class attributes. # # class Account # thread_mattr_accessor :user # end # # Account.user = "DHH" # Account.user # => "DHH" # Account.new.user # => "DHH" # # If a subclass changes the value, the parent class' value is not changed. # Similarly, if the parent class changes the value, the value of subclasses # is not changed. # # class Customer < Account # end # # Customer.user = "Rafael" # Customer.user # => "Rafael" # Account.user # => "DHH" # # To omit the instance writer method, pass instance_writer: false. # To omit the instance reader method, pass instance_reader: false. # # class Current # thread_mattr_accessor :user, instance_writer: false, instance_reader: false # end # # Current.new.user = "DHH" # => NoMethodError # Current.new.user # => NoMethodError # # Or pass instance_accessor: false, to omit both instance methods. # # class Current # thread_mattr_accessor :user, instance_accessor: false # end # # Current.new.user = "DHH" # => NoMethodError # Current.new.user # => NoMethodError def thread_mattr_accessor(*syms, instance_reader: true, instance_writer: true, instance_accessor: true, default: nil, &blk) thread_mattr_reader(*syms, instance_reader: instance_reader, instance_accessor: instance_accessor, default: default, &blk) thread_mattr_writer(*syms, instance_writer: instance_writer, instance_accessor: instance_accessor, default: default) end alias :thread_cattr_accessor :thread_mattr_accessor end