darthjee/core_ext

View on GitHub
lib/darthjee/core_ext/hash/deep_hash_constructor.rb

Summary

Maintainability
A
0 mins
Test Coverage
# frozen_string_literal: true

module Darthjee
  module CoreExt
    module Hash
      # @api private
      #
      # @author Darthjee
      #
      # Class responsible for creating a Hash deep hash
      #
      # Deep hash construction happens when a hash of one layer
      # (no sub hashes) has keys that, once explitted, can be
      # assembled in a hash with many layers
      #
      # @see Transformable#squash
      # @see Transformable#to_deep_hash
      #
      # @example (see Transformable#to_deep_hash)
      # @example General Usage
      #   hash = {
      #     'account.person.name[0]' => 'John',
      #     'account.person.name[1]' => 'Wick',
      #     'account.person.age'     =>  20,
      #     'account.number'         => '102030',
      #     :'house.number'          => 67,
      #     :'house.zip'             => 12_345
      #   }
      #
      #   constructor = Darthjee::CoreExt::Hash::DeepHashConstructor.new('.')
      #
      #   constructor.deep_hash(hash)  # returns {
      #                                #   'account' => {
      #                                #     'person' => {
      #                                #       'name'   => ['John', 'Wick'],
      #                                #       'age'    =>  20
      #                                #     },
      #                                #     'number' => '102030',
      #                                #   },
      #                                #   'house' => {
      #                                #     'number' => 67,
      #                                #     'zip'    => 12345
      #                                #   }
      #                                # }
      class DeepHashConstructor
        autoload :Setter, "#{PATH}/hash/deep_hash_constructor/setter"

        # @param separator [::String] keys splitter
        def initialize(separator = '.')
          @separator = separator
        end

        # Performs deep hash transformation
        #
        # @param hash [::Hash] On layered hash
        #
        # @return [::Hash] Many layered hash
        #
        # @example (see DeepHashConstructor)
        def deep_hash(hash)
          break_keys(hash).tap do
            hash.each do |key, value|
              hash[key] = deep_hash_value(value)
            end
          end
        end

        private

        # @private
        attr_reader :separator

        # @private
        # break the keys creating sub-hashes
        #
        # @param hash [::Hash] hash to be broken
        #
        # @example Breaking many level keys
        #   hash = {
        #     'account.person.name[0]' => 'John',
        #     'account.person.name[1]' => 'Wick',
        #     'account.person.age'     =>  20,
        #     'account.number'         => '102030',
        #     :'house.number'          => 67,
        #     :'house.zip'             => 12_345
        #   }
        #
        #   constructor = Darthjee::CoreExt::Hash::DeepHashConstructor.new('.')
        #
        #   constructor.send(:break_keys, hash)
        #
        #   # Returns {
        #   #   'account' => {
        #   #     %w[person name[0]] => 'John',
        #   #     %w[person name[1]] => 'Wick',
        #   #     %w[person age]     =>  20,
        #   #     %w[number]         => '102030'
        #   #   },
        #   #   'house' => {
        #   #     %w[number] => 67,
        #   #     %w[zip]    => 12_345
        #   #   }
        #   # }
        #
        # @return [Hash]
        def break_keys(hash)
          hash.keys.each do |key|
            value = hash.delete(key)
            base_key, child_key = split_key(key, separator)
            Setter.new(hash, base_key).set(child_key, value)
          end

          hash
        end

        # @private
        #
        # Recursively proccess a value calling deep hash on it
        #
        # @return [::Object]
        def deep_hash_value(object)
          return array_deep_hash(object) if object.is_a? Array
          return deep_hash(object) if object.is_a? Hash
          object
        end

        # @private
        #
        # Map array performing deep hash on its Hash elements
        #
        # @param array [::Array] array to be mapped
        #
        # @return [::Array]
        def array_deep_hash(array)
          array.map do |value|
            deep_hash_value(value)
          end
        end

        # @private
        #
        # Split key into array of keys
        #
        # @param key [::String,::Symbol] key to be splitted
        # @param separator [::String] string of key splitting
        #
        # @return [::Array<::String>,::String]
        def split_key(key, separator)
          keys = key.is_a?(Array) ? key : key.to_s.split(separator)

          return keys unless keys.second

          [keys.shift, keys]
        end
      end
    end
  end
end