lib/darthjee/core_ext/hash/value_changer.rb
# frozen_string_literal: true
module Darthjee
module CoreExt
module Hash
# @api private
# @author darthjee
#
# Class responsible for changing values on a hash
#
# @attribute [::TrueClass,::FalseClass] recursive
# flag telling to apply transformation recursively
# @attribute [::TrueClass,::FalseClass] skip_inner
# flag telling to not apply change block call to inner hashes
# @attribute [::Proc] block
# block to be called when changing the values
#
# @example
# (see #initialize)
#
# @example
# (see #change)
class ValueChanger
# @param [::TrueClass,::FalseClass] recursive
# flag telling to apply transformation recursively
# @param [::TrueClass,::FalseClass] skip_inner
# flag telling to not apply change block call to inner hashes
# @param [::Proc] block
# block to be called when changing the values
#
#
# @example
# changer = Darthjee::CoreExt::Hash::ValueChanger.new(
# recursive: false,
# skip_inner: false,
# &:class
# )
#
# hash = { a: 1, b: { c: 2 }, d: [{ e: 1 }] }
# changer.change(hash) # {
# # a: Integer,
# # b: Hash,
# # d: Array
# # }
def initialize(recursive: true, skip_inner: true, &block)
@recursive = recursive
@skip_inner = skip_inner
@block = block
end
# Change the given object
#
# @return the resulting object (hash or array)
# with it`s values changed
# @param [::Hash/::Array] object
# object to have it's values changed
#
# @example
# changer = Darthjee::CoreExt::Hash::ValueChanger.new do |value|
# value.to_s.size
# end
#
# hash = { a: 15, b: { c: 2 }, d: [{ e: 100 }] }
# changer.change(hash) # {
# # a: 2,
# # b: { c: 1 },
# # d: [{ e: 3 }]
# # }
#
# @example
# changer = Darthjee::CoreExt::Hash::ValueChanger.new(
# skip_inner: true
# ) do |value|
# value.to_s.size
# end
#
# hash = { a: 15, b: { c: 2 }, d: [{ e: 100 }] }
# changer.change(hash) # {
# # a: 2,
# # b: 11,
# # d: 7
# # }
#
# @example
# changer = Darthjee::CoreExt::Hash::ValueChanger.new(
# recursive: true
# ) do |value|
# value.to_s.size
# end
#
# hash = { a: 15, b: { c: 2 }, d: [{ e: 100 }] }
# changer.change(hash) # {
# # a: 2,
# # b: { c: 1 },
# # d: [{ e: 3 }]
# # }
#
# @example
# changer = Darthjee::CoreExt::Hash::ValueChanger.new do |value|
# value.to_s.size
# end
#
# array = [15, { c: 2 }, [{ e: 100 }]]
#
# changer.change(array) # [
# # 2,
# # { c: 1 },
# # [{ e: 3 }]
# # ]
def change(object)
if object.is_a?(Hash)
change_hash(object)
elsif object.is_a?(Array)
change_array(object)
elsif iterable?(object)
change_iterator(object)
else
new_value(object)
end
end
private
attr_reader :recursive, :skip_inner, :block
# @private
#
# Apply change logic to hash object
#
# @param hash [::Hash] hash to be changed
#
# @return [::Hash]
def change_hash(hash)
hash.tap do
hash.each do |key, value|
hash[key] = new_value(value)
end
end
end
# @private
#
# Apply change logic to iterator
#
# @param array [::Array] array to be changed
#
# @return [::Array]
def change_array(array)
array.map!(&method(:change))
end
# @private
#
# Change value from iterator
#
# @param iterator [::Object] object responding to map
#
# @return [::Array]
def change_iterator(iterator)
iterator.map(&method(:change))
end
# @private
#
# Check wehether block should be called over value
#
# when the block is not iterable (not Array or Hash)
# or when skip_inner option is set to be false,
# then block should be called
#
# @param value [::Object] value to be checked
#
# @return [::TrueClass,::FalseClass]
def change_value?(value)
!iterable?(value) || !skip_inner
end
# @private
#
# Checks if a value is iterable
#
# @param value [::Object] object to be checked
#
# @return [::TrueClass,::FalseClass]
def iterable?(value)
value.respond_to?(:map)
end
# @private
#
# Performs recursive transformation
#
# @overload new_value(hash)
# @param hash [::Hash] sub-hash to be processed recursively
# @return [::Hash]
#
# @overload new_value(array)
# @param array [::Array] array to be processed recursively
# @return [::Array]
#
# @overload new_value(value)
# @param value [::Object] value to be transformed
#
# @return [::Object]
def new_value(value)
value = block.call(value) if change_value?(value)
return value unless apply_recursion?(value)
change(value)
end
# @private
#
# Checks if recursion should be applied
#
# @param value [::Object]
#
# @return [::TrueClass,::FalseClass]
def apply_recursion?(value)
iterable?(value) && recursive
end
end
end
end
end