dmolesUC/typesafe_enum

View on GitHub
lib/typesafe_enum/class_methods.rb

Summary

Maintainability
A
0 mins
Test Coverage
module TypesafeEnum
  # Class methods for {{TypesafeEnum::Base}}.
  module ClassMethods
    include Enumerable

    # Returns an array of the enum instances in declaration order
    # @return [Array<self>] All instances of this enum, in declaration order
    def to_a
      as_array.dup
    end

    # Returns the number of enum instances
    # @return [Integer] the number of instances
    def size
      as_array.size
    end

    # Iterates over the set of enum instances
    # @yield [self] Each instance of this enum, in declaration order
    # @return [Enumerator<self>] All instances of this enum, in declaration order
    def each(&block)
      to_a.each(&block)
    end

    # The set of all keys of all enum instances
    # @return [Enumerator<Symbol>] All keys of all enums, in declaration order
    def keys
      to_a.map(&:key)
    end

    # The set of all values of all enum instances
    # @return [Enumerator<Object>] All values of all enums, in declaration order
    def values
      to_a.map(&:value)
    end

    # Iterates over the set of keys of all instances (#keys)
    # @yield [Enumerator<Symbol>] Each key of each instance of this enum, in declaration order
    # @return [Enumerator<Symbol>]
    def each_key(&block)
      keys.each(&block)
    end

    # Iterates over the set of values of all instances (#values)
    # @yield [Enumerator<Object>] Each value of each instance of this enum, in declaration order
    # @return [Enumerator<Object>]
    def each_value(&block)
      values.each(&block)
    end

    # Looks up an enum instance based on its key
    # @param key [Symbol] the key to look up
    # @return [self, nil] the corresponding enum instance, or nil
    def find_by_key(key)
      by_key[key]
    end

    # Looks up an enum instance based on its value
    # @param value [Object] the value to look up
    # @return [self, nil] the corresponding enum instance, or nil
    def find_by_value(value)
      by_value[value]
    end

    # Looks up an enum instance based on its value
    # @param value [Object] the value to look up
    # @return [self, EnumValidationError] the corresponding enum instance, or throws #EnumValidationError
    def find_by_value!(value)
      valid = find_by_value(value)
      return valid unless valid.nil?

      raise Exceptions::EnumValidationError, "#{class_name}: #{value} is absurd"
    end

    # Looks up an enum instance based on the string representation of its value
    # @param value_str [String] the string form of the value
    # @return [self, nil] the corresponding enum instance, or nil
    def find_by_value_str(value_str)
      value_str = value_str.to_s
      by_value_str[value_str]
    end

    # Looks up an enum instance based on the string representation of its value
    # @param value_str [String] the string form of the value
    # @return [self, EnumValidationError] the corresponding enum instance, or throws #EnumValidationError
    def find_by_value_str!(value_str)
      valid = find_by_value_str(value_str)
      return valid unless valid.nil?

      raise Exceptions::EnumValidationError, "#{class_name}: #{value_str} is absurd"
    end

    # Looks up an enum instance based on its ordinal
    # @param ord [Integer] the ordinal to look up
    # @return [self, nil] the corresponding enum instance, or nil
    def find_by_ord(ord)
      return nil if ord > size || ord.negative?

      as_array[ord]
    end

    private

    def by_key
      @by_key ||= {}
    end

    def by_value
      @by_value ||= {}
    end

    def by_value_str
      @by_value_str ||= {}
    end

    def as_array
      @as_array ||= []
    end

    def valid_key_and_value(instance)
      return unless (key = valid_key(instance))

      [key, valid_value(instance)]
    end

    def valid_key(instance)
      key = instance.key
      return key unless (found = find_by_key(key))

      value = instance.value
      raise NameError, "#{name}::#{key} already exists with value #{found.value.inspect}" unless value == found.value

      warn("ignoring redeclaration of #{name}::#{key} with value #{value.inspect} (source: #{caller(6..6).first})")
    end

    def valid_value(instance)
      value = instance.value
      return value unless (found = find_by_value(value))

      key = instance.key
      raise NameError, "A #{name} instance with value #{value.inspect} already exists: #{found.key}" unless key == found.key

      # valid_key() should already have warned us, and valid_key_and_value() should have exited early, but just in case
      # :nocov:
      warn("ignoring redeclaration of #{name}::#{key} with value #{value.inspect} (source: #{caller(6..6).first})")
      # :nocov:
    end

    def register(instance)
      key, value = valid_key_and_value(instance)
      return unless key

      const_set(key.to_s, instance)
      by_key[key] = instance
      by_value[value] = instance
      by_value_str[value.to_s] = instance
      as_array << instance
    end

    # Returns the demodulized class name of the inheriting class
    # @return [String] The demodulized class name
    def class_name
      name.split('::').last
    end

  end
end