Class: ContractValueObject

Inherits:
Object
  • Object
show all
Includes:
Contracts
Defined in:
lib/contract_value_object.rb,
lib/contract_value_object/version.rb,
lib/contract_value_object/error_formatter.rb,
lib/contract_value_object/definition_error.rb

Defined Under Namespace

Classes: DefinitionError, ErrorFormatter

Constant Summary collapse

VERSION =
'0.1.0'

Class Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(**attributes) ⇒ ContractValueObject

Returns a new instance of ContractValueObject.

Raises:



50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/contract_value_object.rb', line 50

def initialize(**attributes)
  error_presenter = self.class.error_presenter
  class_attributes = self.class.attributes
  defaults = self.class.defaults
  error_message_class = DefinitionError::ErrorMessage

  # determine attributes that were not passed in but should have been
  missing_attributes = class_attributes.keys - attributes.keys - defaults.keys
  missing_attribute_errors = missing_attributes.flat_map do |attribute|
    next([]) if class_attributes[attribute].is_a?(Contracts::Optional)
    error_message_class.new(attribute, error_presenter.missing(attribute, class_attributes.fetch(attribute)))
  end

  # determine extraneous attributes that should not have been passed in
  unexpected_attributes = attributes.keys - class_attributes.keys
  unexpected_attribute_errors = unexpected_attributes.map do |attribute|
    error_message_class.new(attribute, error_presenter.unexpected(attribute))
  end

  # set attributes on the object and raise if they do not obey the contract
  setter_errors = defaults.merge(attributes).flat_map do |attribute, value|
    next([]) if unexpected_attributes.include?(attribute)

    begin
      contract = class_attributes.fetch(attribute)
      contract.within_opt_hash! if contract.is_a?(Contracts::Optional)
      # begin support customized setter
      send("#{attribute}=", value)
      set_value = instance_variable_get("@#{attribute}")
      # end support for customized setter
      if Contract.valid?(set_value, contract)
        []
      else
        raise ArgumentError, error_presenter.contract_failure(attribute, set_value, contract)
      end
    rescue self.class::DefinitionError => error
      error_message_class.new(attribute, error.message.gsub("\n", "\n\t"))
    rescue StandardError => error
      error_message_class.new(attribute, error.message)
    end
  end

  errors = missing_attribute_errors + unexpected_attribute_errors + setter_errors
  return if errors.empty?

  raise DefinitionError, errors
end

Class Attribute Details

.error_presenterObject



44
45
46
# File 'lib/contract_value_object.rb', line 44

def error_presenter
  @error_presenter ||= self::ErrorFormatter.new
end

Class Method Details

.attributes(attributes = nil) ⇒ Object



11
12
13
14
15
16
17
18
19
20
21
22
# File 'lib/contract_value_object.rb', line 11

def attributes(attributes = nil)
  if attributes.nil?
    return @attributes if instance_variable_defined?(:@attributes)
    raise ArgumentError, 'Calling for attributes without having defined them.'
  end

  attr_accessor(*attributes.keys)
  attr_writers = attributes.keys.map { |attribute| :"#{attribute}=" }
  private *attr_writers

  @attributes = attributes
end

.defaults(defaults = nil) ⇒ Object

Raises:

  • (ArgumentError)


25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# File 'lib/contract_value_object.rb', line 25

def defaults(defaults = nil)
  return @defaults ||= {} if defaults.nil?

  unexpected_defaults = defaults.keys - attributes.keys
  unless unexpected_defaults.empty?
    raise ArgumentError, "Unexpected defaults are set: #{unexpected_defaults.map(&:to_s).join(', ')}"
  end

  non_optional = defaults.select do |attribute, _|
    !attributes.fetch(attribute).is_a?(Contracts::Optional)
  end
  raise ArgumentError, "Unexpected non-optional defaults: #{non_optional.keys.join(', ')}" unless non_optional.empty?

  @defaults = defaults
end

Instance Method Details

#==(other) ⇒ Object Also known as: eql?



111
112
113
114
115
116
117
# File 'lib/contract_value_object.rb', line 111

def ==(other)
  return super(other) unless self.class == other.class

  self.class.attributes.all? do |attribute, _type|
    public_send(attribute) == other.public_send(attribute)
  end
end

#hashObject

treating contract value objects with the same values as the same object for hashes and sets



107
108
109
# File 'lib/contract_value_object.rb', line 107

def hash
  to_h.hash
end

#inspectObject



121
122
123
# File 'lib/contract_value_object.rb', line 121

def inspect
  "#{self.class} #{to_h.inspect}"
end

#to_hObject



98
99
100
101
102
103
104
# File 'lib/contract_value_object.rb', line 98

def to_h
  key_value_pairs = self.class.attributes.map do |attribute, _type|
    [attribute, public_send(attribute)]
  end

  key_value_pairs.to_h
end