mindreframer/callme

View on GitHub
lib/ext/vendored_activesupport.rb

Summary

Maintainability
B
6 hrs
Test Coverage
##### vendored code from active_support (5.0.0) for Class.class_attribute  ####
# require 'active_support/core_ext/class/attribute'
# require 'active_support/core_ext/kernel/singleton_class'
# require 'active_support/core_ext/module/remove_method'
# require 'active_support/core_ext/array/extract_options'

unless defined?(ActiveSupport)
  class Module
    def remove_possible_method(method)
      if method_defined?(method) || private_method_defined?(method)
        undef_method(method)
      end
    end

    def redefine_method(method, &block)
      remove_possible_method(method)
      define_method(method, &block)
    end
  end

  module Kernel
    # class_eval on an object acts like singleton_class.class_eval.
    def class_eval(*args, &block)
      singleton_class.class_eval(*args, &block)
    end
  end

  class Hash
    # By default, only instances of Hash itself are extractable.
    # Subclasses of Hash may implement this method and return
    # true to declare themselves as extractable. If a Hash
    # is extractable, Array#extract_options! pops it from
    # the Array when it is the last element of the Array.
    def extractable_options?
      instance_of?(Hash)
    end
  end

  class Array
    # Extracts options from a set of arguments. Removes and returns the last
    # element in the array if it's a hash, otherwise returns a blank hash.
    #
    #   def options(*args)
    #     args.extract_options!
    #   end
    #
    #   options(1, 2)        # => {}
    #   options(1, 2, a: :b) # => {:a=>:b}
    def extract_options!
      if last.is_a?(Hash) && last.extractable_options?
        pop
      else
        {}
      end
    end
  end

  class Class
    # Declare a class-level attribute whose value is inheritable by subclasses.
    # Subclasses can change their own value and it will not impact parent class.
    #
    #   class Base
    #     class_attribute :setting
    #   end
    #
    #   class Subclass < Base
    #   end
    #
    #   Base.setting = true
    #   Subclass.setting            # => true
    #   Subclass.setting = false
    #   Subclass.setting            # => false
    #   Base.setting                # => true
    #
    # In the above case as long as Subclass does not assign a value to setting
    # by performing <tt>Subclass.setting = _something_ </tt>, <tt>Subclass.setting</tt>
    # would read value assigned to parent class. Once Subclass assigns a value then
    # the value assigned by Subclass would be returned.
    #
    # This matches normal Ruby method inheritance: think of writing an attribute
    # on a subclass as overriding the reader method. However, you need to be aware
    # when using +class_attribute+ with mutable structures as +Array+ or +Hash+.
    # In such cases, you don't want to do changes in places but use setters:
    #
    #   Base.setting = []
    #   Base.setting                # => []
    #   Subclass.setting            # => []
    #
    #   # Appending in child changes both parent and child because it is the same object:
    #   Subclass.setting << :foo
    #   Base.setting               # => [:foo]
    #   Subclass.setting           # => [:foo]
    #
    #   # Use setters to not propagate changes:
    #   Base.setting = []
    #   Subclass.setting += [:foo]
    #   Base.setting               # => []
    #   Subclass.setting           # => [:foo]
    #
    # For convenience, an instance predicate method is defined as well.
    # To skip it, pass <tt>instance_predicate: false</tt>.
    #
    #   Subclass.setting?       # => false
    #
    # Instances may overwrite the class value in the same way:
    #
    #   Base.setting = true
    #   object = Base.new
    #   object.setting          # => true
    #   object.setting = false
    #   object.setting          # => false
    #   Base.setting            # => true
    #
    # To opt out of the instance reader method, pass <tt>instance_reader: false</tt>.
    #
    #   object.setting          # => NoMethodError
    #   object.setting?         # => NoMethodError
    #
    # To opt out of the instance writer method, pass <tt>instance_writer: false</tt>.
    #
    #   object.setting = false  # => NoMethodError
    #
    # To opt out of both instance methods, pass <tt>instance_accessor: false</tt>.
    def class_attribute(*attrs)
      options = attrs.extract_options!
      instance_reader = options.fetch(:instance_accessor, true) && options.fetch(:instance_reader, true)
      instance_writer = options.fetch(:instance_accessor, true) && options.fetch(:instance_writer, true)
      instance_predicate = options.fetch(:instance_predicate, true)

      attrs.each do |name|
        define_singleton_method(name) { nil }
        define_singleton_method("#{name}?") { !!public_send(name) } if instance_predicate

        ivar = "@#{name}"

        define_singleton_method("#{name}=") do |val|
          singleton_class.class_eval do
            remove_possible_method(name)
            define_method(name) { val }
          end

          if singleton_class?
            class_eval do
              remove_possible_method(name)
              define_method(name) do
                if instance_variable_defined? ivar
                  instance_variable_get ivar
                else
                  singleton_class.send name
                end
              end
            end
          end
          val
        end

        if instance_reader
          remove_possible_method name
          define_method(name) do
            if instance_variable_defined?(ivar)
              instance_variable_get ivar
            else
              self.class.public_send name
            end
          end
          define_method("#{name}?") { !!public_send(name) } if instance_predicate
        end

        attr_writer name if instance_writer
      end
    end

    private

      unless respond_to?(:singleton_class?)
        def singleton_class?
          ancestors.first != self
        end
      end
  end
end