Skip to content

Commit

Permalink
catch ignored key => value pairs for Hash Schema as nested values in …
Browse files Browse the repository at this point in the history
…the ':__ignored_data' special key
  • Loading branch information
Pieter Vanmeerbeek committed Dec 15, 2016
1 parent 261ba03 commit e2ac4c0
Show file tree
Hide file tree
Showing 2 changed files with 53 additions and 9 deletions.
58 changes: 51 additions & 7 deletions lib/dry/types/hash/schema.rb
Expand Up @@ -13,6 +13,8 @@ class Schema < Hash
# @return [Hash{Symbol => Definition}]
attr_reader :member_types

IGNORED_DATA_KEY = :__ignored_data

# @param [Class] _primitive
# @param [Hash] options
# @option options [Hash{Symbol => Definition}] :member_types
Expand All @@ -38,12 +40,23 @@ def try(hash, &block)
output = {}

begin
result = try_coerce(hash) do |key, member_result|
result, ignored_data = try_coerce(hash) do |key, member_result|
success &&= member_result.success?
output[key] = member_result.input

if ignored_member_data = gather_ignored_data(output[key])
output[IGNORED_DATA_KEY] ||= {}
output[IGNORED_DATA_KEY][key] = ignored_member_data
end

member_result
end

if ignored_data && ! ignored_data.empty?
output[IGNORED_DATA_KEY] ||= {}
output[IGNORED_DATA_KEY] = ignored_data.merge(output[IGNORED_DATA_KEY])
end

rescue ConstraintError, UnknownKeysError, SchemaError => e
success = false
result = e
Expand All @@ -59,6 +72,24 @@ def try(hash, &block)

private

def gather_ignored_data(output)
if output.class.to_s == 'Hash' && output.has_key?(IGNORED_DATA_KEY) # TODO somehow output.is_a?(Hash) always seems to return false ...
output.delete(IGNORED_DATA_KEY)
elsif output.class.to_s == 'Array' # TODO somehow output.is_a?(Array) always seems to return false ...
output.inject([]) do |ignored_array_data, element|
ignored_data = gather_ignored_data(element)
ignored_array_data << ignored_data if ignored_data.present?
end
end
end

def resolve_ignored_values(hash)
(hash.keys - member_types.keys).inject({}) do |ignored_data,k|
ignored_data[k] = hash[k]
ignored_data
end
end

# @param [Hash] hash
# @return [Hash{Symbol => Object}]
def try_coerce(hash)
Expand All @@ -70,27 +101,31 @@ def try_coerce(hash)
# @param [Hash] hash
# @return [Hash{Symbol => Object}]
def coerce(hash)
resolve(hash) do |type, key, value|
result, ignored_values = resolve(hash) do |type, key, value|
begin
type.call(value)
rescue ConstraintError
raise SchemaError.new(key, value)
end
end
result
end

# @param [Hash] hash
# @return [Hash{Symbol => Object}]
def resolve(hash)
result = {}

ignored_values = resolve_ignored_values(hash)

member_types.each do |key, type|
if hash.key?(key)
result[key] = yield(type, key, hash[key])
else
resolve_missing_value(result, key, type)
end
end
result
[result,ignored_values]
end

# @param [Hash] result
Expand Down Expand Up @@ -136,15 +171,17 @@ class Strict < Permissive
# @raise [UnknownKeysError]
# if there any unexpected key in given hash
def resolve(hash)
unexpected = hash.keys - member_types.keys
raise UnknownKeysError.new(*unexpected) unless unexpected.empty?

super do |member_type, key, value|
type = member_type.default? ? member_type.type : member_type

yield(type, key, value)
end
end

def resolve_ignored_values(hash)
unexpected = hash.keys - member_types.keys
raise UnknownKeysError.new(*unexpected) unless unexpected.empty?
end
end

# {StrictWithDefaults} checks that there are no extra keys
Expand Down Expand Up @@ -203,8 +240,15 @@ def try(hash, &block)
class Symbolized < Weak
private


def resolve(hash)
result = {}

ignored_values = (hash.keys.map(&:to_sym) - member_types.keys).inject({}) do |ignored_data,k|
ignored_data[k] = hash[k]
ignored_data
end

member_types.each do |key, type|
keyname =
if hash.key?(key)
Expand All @@ -219,7 +263,7 @@ def resolve(hash)
resolve_missing_value(result, key, type)
end
end
result
[result,ignored_values]
end
end

Expand Down
4 changes: 2 additions & 2 deletions spec/dry/types/compiler_spec.rb
Expand Up @@ -208,11 +208,11 @@
expect(hash['oops']).to eql('oops')

expect(hash['foo' => 'bar', 'email' => 'jane@doe.org', 'age' => '20', 'admin' => '1']).to eql(
email: 'jane@doe.org', age: 20, admin: true
email: 'jane@doe.org', age: 20, admin: true, __ignored_data: {foo: nil}
)

expect(hash['foo' => 'bar', 'age' => '20', 'admin' => '1']).to eql(
age: 20, admin: true
age: 20, admin: true, __ignored_data: {foo: nil}
)
end

Expand Down

0 comments on commit e2ac4c0

Please sign in to comment.