diff --git a/lib/values.rb b/lib/values.rb index 3c8af8a..6bc5874 100644 --- a/lib/values.rb +++ b/lib/values.rb @@ -49,13 +49,15 @@ def initialize(*values) const_set :VALUE_ATTRS, fields def self.with(hash) - unexpected_keys = hash.keys - self::VALUE_ATTRS - if unexpected_keys.any? + num_recognized_keys = self::VALUE_ATTRS.count { |field| hash.key?(field) } + + if num_recognized_keys != hash.size + unexpected_keys = hash.keys - self::VALUE_ATTRS raise ArgumentError.new("Unexpected hash keys: #{unexpected_keys}") end - missing_keys = self::VALUE_ATTRS - hash.keys - if missing_keys.any? + if num_recognized_keys != self::VALUE_ATTRS.size + missing_keys = self::VALUE_ATTRS - hash.keys raise ArgumentError.new("Missing hash keys: #{missing_keys} (got keys #{hash.keys})") end @@ -94,9 +96,22 @@ def pretty_print(q) end end + # Optimized to avoid intermediate Hash instantiations. def with(hash = {}) return self if hash.empty? - self.class.with(to_h.merge(hash)) + + num_recognized_keys = self.class::VALUE_ATTRS.count { |field| hash.key?(field) } + + if num_recognized_keys != hash.size + unexpected_keys = hash.keys - self.class::VALUE_ATTRS + raise ArgumentError.new("Unexpected hash keys: #{unexpected_keys}") + end + + args = self.class::VALUE_ATTRS.map do |field| + hash.key?(field) ? hash[field] : send(field) + end + + self.class.new(*args) end def to_h