alienfast/dry-config

View on GitHub
lib/dry/config/deep_symbolizable.rb

Summary

Maintainability
A
1 hr
Test Coverage
# Slightly modified from https://gist.github.com/morhekil/998709

# Symbolizes all of hash's keys and subkeys.
# Also allows for custom pre-processing of keys (e.g. downcasing, etc)
# if the block is given:
#
# somehash.deep_symbolize { |key| key.downcase }
#
# Usage: either include it into global Hash class to make it available to
#        to all hashes, or extend only your own hash objects with this
#        module.
#        E.g.:
#        1) class Hash; include DeepSymbolizable; end
#        2) myhash.extend DeepSymbolizable
module Dry
  module Config

    module DeepSymbolizable
      def deep_symbolize(invert = false, &block)
        method = self.class.to_s.downcase.to_sym
        symbolizers = DeepSymbolizable::Symbolizers
        symbolizers.respond_to?(method) ? symbolizers.send(method, self, invert, &block) : self
      end

      module Symbolizers
        extend self

        # the primary method - symbolizes keys of the given hash,
        # preprocessing them with a block if one was given, and recursively
        # going into all nested enumerables
        def hash(hash, invert, &block)
          hash.inject({}) do |result, (key, value)|
            # Recursively deep-symbolize subhashes
            value = _recurse_(value, invert, &block)

            # Pre-process the key with a block if it was given
            key = yield key if block_given?

            if invert
              # UN-Symbolize the key
              s_key = key.to_s rescue key

              # write it back into the result and return the updated hash
              result[s_key] = value

            else
              # Symbolize the key string if it responds to to_sym
              sym_key = key.to_sym rescue key

              # write it back into the result and return the updated hash
              result[sym_key] = value
            end
            result
          end
        end

        # walking over arrays and symbolizing all nested elements
        def array(ary, invert, &block)
          ary.map { |v| _recurse_(v, invert, &block) }
        end

        # handling recursion - any Enumerable elements (except String)
        # is being extended with the module, and then symbolized
        def _recurse_(value, invert, &block)
          if value.is_a?(Enumerable) && !value.is_a?(String)
            # support for a use case without extended core Hash
            value.extend DeepSymbolizable unless value.class.include?(DeepSymbolizable)
            value = value.deep_symbolize(invert, &block)
          end
          value
        end
      end

    end
  end
end

# include in all Hash objects
class Hash;
  include Dry::Config::DeepSymbolizable;
end