AndyObtiva/easily_typable

View on GitHub
lib/easily_typable.rb

Summary

Maintainability
A
0 mins
Test Coverage
module EasilyTypable

  def self.included(class_constant)
    super
    class_constant.class_eval <<-end_eval
      def #{__methodize__(class_constant.name)}?
        self.is_a?(#{class_constant.name})
      end
      def self.inherited(subclass_constant)
        super
        subclass_constant.send :include, SubClassMethods
      end
    end_eval
  end

  # NOTE: Rails Specific Feature
  # Retroactively load Rails model class matching missing method name to add to
  # EasilyTypable hierarchy when it fails on first attempt.
  # This shall result in it working on second attempt if model class exists.
  # Example:
  alias method_missing_without_easily_typable method_missing
  def method_missing(name, *args, &block)
    if name.to_s.end_with?('?') && !@easily_typable_class_load_attempted
      # attempt to load Rails class and re-invoke once
      class_name = name.to_s.sub(/\?$/, '').split('_').map(&:capitalize).join
      Object.const_get(class_name) rescue nil
      @easily_typable_class_load_attempted = true
      send(name, *args, &block)
    else
      method_missing_without_easily_typable(name, *args, &block)
    end
  end

  module SubClassMethods
    def self.included(subclass_constant)
      super
      subclass_constant.class_eval(type_method_definition(subclass_constant))
      superclass_constant = subclass_constant.superclass
      begin
        superclass_constant.class_eval(type_method_definition(subclass_constant))
        superclass_constant = superclass_constant.superclass
      end while superclass_constant.include?(EasilyTypable)
    end

    private
    def self.type_method_definition(subclass_constant)
      <<-MULTI
        def #{EasilyTypable::__methodize__(subclass_constant.name)}?
          self.is_a?(#{subclass_constant.name})
        end
      MULTI
    end
  end

  protected
  #added this method to break reliance on ActiveSupport and Facets
  def self.__methodize__(text)
    text.split(/::/).last.gsub(/([A-Z]+)([A-Z])/,'\1_\2').
    gsub(/([a-z])([A-Z])/,'\1_\2').
    downcase
  end

end