diff --git a/lib/contracted_value/core.rb b/lib/contracted_value/core.rb index 0631699..ef03920 100644 --- a/lib/contracted_value/core.rb +++ b/lib/contracted_value/core.rb @@ -182,10 +182,17 @@ def extract_value(hash) attr_reader :default_value def raise_error_if_inputs_invalid + raise_error_if_name_invalid raise_error_if_refrigeration_mode_invalid raise_error_if_default_value_invalid end + def raise_error_if_name_invalid + return if name.is_a?(Symbol) + + raise NotImplementedError, "Internal error: name is not a symbol (#{name.class.name})" + end + def raise_error_if_refrigeration_mode_invalid return if RefrigerationMode::Enum.all.include?(refrigeration_mode) @@ -231,6 +238,8 @@ def initialize(input_attr_values = {}) ) end + @attr_values = {} + self.class.send(:attribute_set).each_attribute do |attribute| attr_value = attribute.extract_value(input_attr_values_hash) @@ -253,8 +262,8 @@ def initialize(input_attr_values = {}) # Using symbol since attribute names are limited in number # An alternative would be using frozen string - instance_variable_set( - :"@#{attribute.name}", + @attr_values.store( + attribute.name.to_sym, sometimes_frozen_attr_value, ) end @@ -265,10 +274,7 @@ def initialize(input_attr_values = {}) # rubocop:enable Metrics/CyclomaticComplexity def to_h - self.class.send(:attribute_set). - each_attribute.each_with_object({}) do |attribute, hash| - hash[attribute.name] = instance_variable_get(:"@#{attribute.name}") - end + @attr_values.clone end # == Class interface == # @@ -288,16 +294,19 @@ def attribute( refrigeration_mode: RefrigerationMode::Enum::DEEP, default_value: Private::ATTR_DEFAULT_VALUE_ABSENT_VAL ) + # Using symbol since attribute names are limited in number + # An alternative would be using frozen string + name_in_sym = name.to_sym attr = Attribute.new( - name: name, + name: name_in_sym, contract: contract, refrigeration_mode: refrigeration_mode, default_value: default_value, ) @attribute_set = @attribute_set.add(attr) - attr_reader(name) + define_method(name_in_sym) { @attr_values[name_in_sym] } end # @api private diff --git a/spec/contracted_value/value_spec.rb b/spec/contracted_value/value_spec.rb index 0673bbe..1fcaedd 100644 --- a/spec/contracted_value/value_spec.rb +++ b/spec/contracted_value/value_spec.rb @@ -39,6 +39,22 @@ end }.to raise_error(::ContractedValue::Errors::DuplicateAttributeDeclaration) end + + example "does not raise error when declaring 1 attribute with string name" do + expect { + value_class.class_eval do + attribute("attribute_1") + end + }.to_not raise_error + end + + example "does not raise error when declaring 1 attribute with number name" do + expect { + value_class.class_eval do + attribute(1) + end + }.to raise_error(::NoMethodError, /undefined method `to_sym'/) + end end @@ -93,13 +109,19 @@ it "does not raise error when input is a value" do aggregate_failures do + new_val = nil expect { - value_class.new( + new_val = value_class.new( value_class.new( default_inputs, ), ) }.to_not raise_error + if new_val + default_inputs.each_pair do |attr_name, attr_val| + expect(new_val.public_send(attr_name)).to eq(attr_val) + end + end end end