Sage/hash_kit

View on GitHub
lib/hash_kit/helper.rb

Summary

Maintainability
A
3 hrs
Test Coverage
A
96%
# frozen_string_literal: true

module HashKit
  # Hash kit Helper class
  class Helper
    # This method is called to make a hash allow indifferent access (it will
    # accept both strings & symbols for a valid key).
    def indifferent!(hash)
      return unless hash.is_a?(Hash)

      # Set the default proc to allow the key to be either string or symbol if
      # a matching key is found.
      hash.default_proc = proc do |h, k|
        if h.key?(k.to_s)
          h[k.to_s]
        elsif h.key?(k.to_sym)
          h[k.to_sym]
        else
          nil
        end
      end

      # Recursively process any child hashes
      hash.each do |key,value|
        unless hash[key].nil?
          if hash[key].is_a?(Hash)
            indifferent!(hash[key])
          elsif hash[key].is_a?(Array)
            indifferent_array!(hash[key])
          end
        end
      end

      hash
    end

    def indifferent_array!(array)
      return unless array.is_a?(Array)

      array.each do |i|
        if i.is_a?(Hash)
          indifferent!(i)
        elsif i.is_a?(Array)
          indifferent_array!(i)
        end
      end
    end

    # This method is called to convert all the keys of a hash into symbols to
    # allow consistent usage of hashes within your Ruby application.
    def symbolize(hash)
      {}.tap do |h|
        hash.each { |key, value| h[key.to_sym] = map_value_symbol(value) }
      end
    end

    # This method is called to convert all the keys of a hash into strings to
    # allow consistent usage of hashes within your Ruby application.
    def stringify(hash)
      {}.tap do |h|
        hash.each { |key, value| h[key.to_s] = map_value_string(value) }
      end
    end

    # Convert an object to a hash representation of its instance variables.
    # @return [Hash] if the object is not nil, otherwise nil is returned.
    def to_hash(obj)
      return nil unless obj
      return obj if obj.is_a?(Hash)

      hash = {}
      obj.instance_variables.each do |key|
        hash[key[1..-1].to_sym] = deeply_to_hash(obj.instance_variable_get(key))
      end
      hash
    end

    # Return an object of type klass from the values in the given hash
    #
    # @param [Hash] hash
    # @param [Class] klass
    # @param [Array] transforms
    # @return [Object]
    def from_hash(hash, klass, transforms = [])
      obj = klass.new
      return obj if hash.nil? || hash.empty?
      raise ArgumentError, "#{hash.inspect} is not a hash" unless hash.is_a?(Hash)

      hash.each do |k, v|
        next unless obj.respond_to?(k)

        transform = transforms.detect { |t| t.key.to_sym == k.to_sym }
        if !transform.nil?
          if v.is_a?(Hash)
            child = from_hash(v, transform.klass, transforms)
            obj.instance_variable_set("@#{k}", child)
          elsif v.is_a?(Array)
            items = v.map do |i|
              from_hash(i, transform.klass, transforms)
            end
            obj.instance_variable_set("@#{k}", items)
          end
        else
          obj.instance_variable_set("@#{k}", v)
        end
      end

      obj
    end

    private

    # nodoc
    def convert_hash_values(hash)
      new_hash = {}
      hash.each do |key, val|
        new_hash[key] = deeply_to_hash(val)
      end
      new_hash
    end

    # nodoc
    def convert_array_values(array)
      new_array = []
      array.each_index do |index|
        new_array[index] = deeply_to_hash(array[index])
      end
      new_array
    end

    def standard_type?(obj)
      [
        String, Numeric, Float, Date, DateTime, Time, Integer, TrueClass, FalseClass, NilClass, Symbol
      ].detect do |klass|
        obj.is_a?(klass)
      end
    end

    # nodoc
    def deeply_to_hash(val)
      if val.is_a?(Hash)
        return convert_hash_values(val)
      elsif val.is_a?(Array)
        return convert_array_values(val)
      elsif standard_type?(val)
        val
      else
        return to_hash(val)
      end
    end

    def map_value_symbol(thing)
      case thing
        when Hash
          symbolize(thing)
        when Array
          thing.map { |v| map_value_symbol(v) }
        else
          thing
      end
    end

    def map_value_string(thing)
      case thing
        when Hash
          stringify(thing)
        when Array
          thing.map { |v| map_value_string(v) }
        else
          thing
      end
    end
  end
end