From faa6f8b7606a413745d61d436901ebcb05b8dc9a Mon Sep 17 00:00:00 2001 From: Matt Schreiber Date: Mon, 7 Dec 2015 19:04:00 -0500 Subject: [PATCH 1/2] Added exception class that allows 'introspection' of missing/unexpected keys in initialize() --- lib/values.rb | 26 +++++++++++++++++++------- spec/values_spec.rb | 25 +++++++++++++++++++++---- 2 files changed, 40 insertions(+), 11 deletions(-) diff --git a/lib/values.rb b/lib/values.rb index c762350..92de66b 100644 --- a/lib/values.rb +++ b/lib/values.rb @@ -10,6 +10,17 @@ # p.y # #=> 0 # +module Values + class ArgumentError < ::ArgumentError + attr_reader :missing_keys, :unexpected_keys + + def initialize(message, missing_keys=nil, unexpected_keys=nil) + @missing_keys, @unexpected_keys = missing_keys, unexpected_keys + super(message) + end + end +end + class Value # Create a new value class. # @@ -24,7 +35,9 @@ def self.new(*fields, &block) attr_reader(:hash, *fields) define_method(:initialize) do |*values| - raise ArgumentError.new("wrong number of arguments, #{values.size} for #{fields.size}") if fields.size != values.size + if (fields.size != values.size) + raise Values::ArgumentError.new("wrong number of arguments, #{values.size} for #{fields.size}", fields[values.size..-1]) + end fields.zip(values) do |field, value| instance_variable_set(:"@#{field}", value) @@ -39,13 +52,12 @@ def self.new(*fields, &block) def self.with(hash) unexpected_keys = hash.keys - self::VALUE_ATTRS - if unexpected_keys.any? - raise ArgumentError.new("Unexpected hash keys: #{unexpected_keys}") - end - missing_keys = self::VALUE_ATTRS - hash.keys - if missing_keys.any? - raise ArgumentError.new("Missing hash keys: #{missing_keys} (got keys #{hash.keys})") + + if unexpected_keys.any? + raise Values::ArgumentError.new("Unexpected hash keys: #{unexpected_keys}", missing_keys, unexpected_keys) + elsif missing_keys.any? + raise Values::ArgumentError.new("Missing hash keys: #{missing_keys} (got keys #{hash.keys})", missing_keys, unexpected_keys) end new(*hash.values_at(*self::VALUE_ATTRS)) diff --git a/spec/values_spec.rb b/spec/values_spec.rb index e62ce56..1d4d885 100644 --- a/spec/values_spec.rb +++ b/spec/values_spec.rb @@ -42,7 +42,12 @@ end it 'raises argument errors if not given the right number of arguments' do - expect { Point.new }.to raise_error(ArgumentError, 'wrong number of arguments, 0 for 2') + expect { Point.new }.to raise_error do |error| + expect(error).to be_a(Values::ArgumentError) + expect(error.message).to eq('wrong number of arguments, 0 for 2') + expect(error.missing_keys).to contain_exactly(:x, :y) + expect(error.unexpected_keys).to be_nil + end end end @@ -100,11 +105,19 @@ def change_color(new_color) end it 'errors if you instantiate it from a hash with unrecognised fields' do - expect { Money.with(:unrecognized_field => 1, :amount => 2, :denomination => 'USD') }.to raise_error(ArgumentError) + expect { Money.with(:unrecognized_field => 1, :amount => 2, :denomination => 'USD') }.to raise_error do |error| + expect(error).to be_a(Values::ArgumentError) + expect(error.missing_keys).to be_empty + expect(error.unexpected_keys).to contain_exactly(:unrecognized_field) + end end it 'errors if you instantiate it from a hash with missing fields' do - expect { Money.with({}) }.to raise_error(ArgumentError) + expect { Money.with({}) }.to raise_error do |error| + expect(error).to be_a(Values::ArgumentError) + expect(error.missing_keys).to contain_exactly(:amount, :denomination) + expect(error.unexpected_keys).to be_empty + end end it 'does not error when fields are explicitly nil' do @@ -200,7 +213,11 @@ def change_color(new_color) end it 'raises argument error if unknown field' do - expect { p.with({ :foo => 3 }) }.to raise_error(ArgumentError) + expect { p.with({ :foo => 3 , :bar => "baz" }) }.to raise_error do |error| + expect(error).to be_a(Values::ArgumentError) + expect(error.unexpected_keys).to contain_exactly(:foo, :bar) + expect(error.missing_keys).to be_empty + end end end end From c8e50f16f144dbd9bd30b1b7fb9bb30cec43c740 Mon Sep 17 00:00:00 2001 From: Matt Schreiber Date: Wed, 9 Dec 2015 07:43:28 -0500 Subject: [PATCH 2/2] Updated lib/values.rb and spec/values_spec.rb in response to PR comments 1. {missing,unexpected}_keys => {missing,unexpected}_values 2. Values::ArgumentError => Values::FieldError 3. Default values for @{missing,unexpected}_fields now [] instead of nil --- lib/values.rb | 22 +++++++++++----------- spec/values_spec.rb | 24 ++++++++++++------------ 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/lib/values.rb b/lib/values.rb index 92de66b..e59736c 100644 --- a/lib/values.rb +++ b/lib/values.rb @@ -11,11 +11,11 @@ # #=> 0 # module Values - class ArgumentError < ::ArgumentError - attr_reader :missing_keys, :unexpected_keys + class FieldError < ::ArgumentError + attr_reader :missing_fields, :unexpected_fields - def initialize(message, missing_keys=nil, unexpected_keys=nil) - @missing_keys, @unexpected_keys = missing_keys, unexpected_keys + def initialize(message, missing_fields=[], unexpected_fields=[]) + @missing_fields, @unexpected_fields = missing_fields, unexpected_fields super(message) end end @@ -36,7 +36,7 @@ def self.new(*fields, &block) define_method(:initialize) do |*values| if (fields.size != values.size) - raise Values::ArgumentError.new("wrong number of arguments, #{values.size} for #{fields.size}", fields[values.size..-1]) + raise Values::FieldError.new("wrong number of arguments, #{values.size} for #{fields.size}", fields[values.size..-1]) end fields.zip(values) do |field, value| @@ -51,13 +51,13 @@ def self.new(*fields, &block) const_set :VALUE_ATTRS, fields def self.with(hash) - unexpected_keys = hash.keys - self::VALUE_ATTRS - missing_keys = self::VALUE_ATTRS - hash.keys + unexpected_fields = hash.keys - self::VALUE_ATTRS + missing_fields = self::VALUE_ATTRS - hash.keys - if unexpected_keys.any? - raise Values::ArgumentError.new("Unexpected hash keys: #{unexpected_keys}", missing_keys, unexpected_keys) - elsif missing_keys.any? - raise Values::ArgumentError.new("Missing hash keys: #{missing_keys} (got keys #{hash.keys})", missing_keys, unexpected_keys) + if unexpected_fields.any? + raise Values::FieldError.new("Unexpected hash keys: #{unexpected_fields}", missing_fields, unexpected_fields) + elsif missing_fields.any? + raise Values::FieldError.new("Missing hash keys: #{missing_fields} (got keys #{hash.keys})", missing_fields, unexpected_fields) end new(*hash.values_at(*self::VALUE_ATTRS)) diff --git a/spec/values_spec.rb b/spec/values_spec.rb index 1d4d885..1a95dc1 100644 --- a/spec/values_spec.rb +++ b/spec/values_spec.rb @@ -43,10 +43,10 @@ it 'raises argument errors if not given the right number of arguments' do expect { Point.new }.to raise_error do |error| - expect(error).to be_a(Values::ArgumentError) + expect(error).to be_a(Values::FieldError) expect(error.message).to eq('wrong number of arguments, 0 for 2') - expect(error.missing_keys).to contain_exactly(:x, :y) - expect(error.unexpected_keys).to be_nil + expect(error.missing_fields).to contain_exactly(:x, :y) + expect(error.unexpected_fields).to be_empty end end end @@ -106,17 +106,17 @@ def change_color(new_color) it 'errors if you instantiate it from a hash with unrecognised fields' do expect { Money.with(:unrecognized_field => 1, :amount => 2, :denomination => 'USD') }.to raise_error do |error| - expect(error).to be_a(Values::ArgumentError) - expect(error.missing_keys).to be_empty - expect(error.unexpected_keys).to contain_exactly(:unrecognized_field) + expect(error).to be_a(Values::FieldError) + expect(error.missing_fields).to be_empty + expect(error.unexpected_fields).to contain_exactly(:unrecognized_field) end end it 'errors if you instantiate it from a hash with missing fields' do expect { Money.with({}) }.to raise_error do |error| - expect(error).to be_a(Values::ArgumentError) - expect(error.missing_keys).to contain_exactly(:amount, :denomination) - expect(error.unexpected_keys).to be_empty + expect(error).to be_a(Values::FieldError) + expect(error.missing_fields).to contain_exactly(:amount, :denomination) + expect(error.unexpected_fields).to be_empty end end @@ -214,9 +214,9 @@ def change_color(new_color) it 'raises argument error if unknown field' do expect { p.with({ :foo => 3 , :bar => "baz" }) }.to raise_error do |error| - expect(error).to be_a(Values::ArgumentError) - expect(error.unexpected_keys).to contain_exactly(:foo, :bar) - expect(error.missing_keys).to be_empty + expect(error).to be_a(Values::FieldError) + expect(error.unexpected_fields).to contain_exactly(:foo, :bar) + expect(error.missing_fields).to be_empty end end end