Module: ValueSemantics::InstanceMethods

Defined in:
lib/value_semantics/instance_methods.rb

Overview

All the instance methods available on ValueSemantics objects

Instance Method Summary collapse

Instance Method Details

#==(other) ⇒ Boolean

Loose equality



127
128
129
# File 'lib/value_semantics/instance_methods.rb', line 127

def ==(other)
  (other.is_a?(self.class) || is_a?(other.class)) && other.to_h.eql?(to_h)
end

#[](attr_name) ⇒ Object

Returns the value for the given attribute name

Raises:



90
91
92
93
94
95
96
97
98
99
100
# File 'lib/value_semantics/instance_methods.rb', line 90

def [](attr_name)
  attr = self.class.value_semantics.attributes.find do |attr|
    attr.name.equal?(attr_name)
  end

  if attr
    public_send(attr_name)
  else
    raise UnrecognizedAttributes, "`#{self.class}` has no attribute named `#{attr_name.inspect}`"
  end
end

#deconstruct_keys(_) ⇒ Object



166
167
168
# File 'lib/value_semantics/instance_methods.rb', line 166

def deconstruct_keys(_)
  to_h
end

#eql?(other) ⇒ Boolean

Strict equality



137
138
139
# File 'lib/value_semantics/instance_methods.rb', line 137

def eql?(other)
  other.class.equal?(self.class) && other.to_h.eql?(to_h)
end

#hashObject

Unique-ish integer, based on attributes and class of the object



144
145
146
# File 'lib/value_semantics/instance_methods.rb', line 144

def hash
  to_h.hash ^ self.class.hash
end

#initialize(attributes = nil) ⇒ Object

Creates a value object based on a hash of attributes

Raises:

  • (UnrecognizedAttributes)

    if given_attrs contains keys that are not attributes

  • (MissingAttributes)

    if given_attrs is missing any attributes that do not have defaults

  • (InvalidValue)

    if any attribute values do no pass their validators

  • (TypeError)

    if the argument does not respond to #to_h



19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
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
# File 'lib/value_semantics/instance_methods.rb', line 19

def initialize(attributes = nil)
  attributes_hash =
    if attributes.respond_to?(:to_h)
      attributes.to_h
    else
      raise TypeError, <<-END_MESSAGE.strip.gsub(/\s+/, ' ')
        Can not initialize a `#{self.class}` with a `#{attributes.class}`
        object. This argument is typically a `Hash` of attributes, but can
        be any object that responds to `#to_h`.
      END_MESSAGE
    end

  remaining_attrs = attributes_hash.keys
  missing_attrs = nil
  invalid_attrs = nil

  self.class.value_semantics.attributes.each do |attr|
    if remaining_attrs.delete(attr.name)
      value = attributes_hash.fetch(attr.name)
    elsif attr.optional?
      value = attr.default_generator.()
    else
      missing_attrs ||= []
      missing_attrs << attr.name
      next
    end

    coerced_value = attr.coerce(value, self.class)
    if attr.validate?(coerced_value)
      instance_variable_set(attr.instance_variable, coerced_value)
    else
      invalid_attrs ||= {}
      invalid_attrs[attr.name] = coerced_value
    end
  end

  # TODO: aggregate all exceptions raised from #initialize into one big
  # exception that explains everything that went wrong, instead of multiple
  # smaller exceptions. Unfortunately, this would not be backwards
  # compatible.
  unless remaining_attrs.empty?
    raise UnrecognizedAttributes.new(
      "`#{self.class}` does not define attributes: " +
        remaining_attrs.map { |k| '`' + k.inspect + '`' }.join(', ')
    )
  end

  if missing_attrs
    raise MissingAttributes.new(
      "Some attributes required by `#{self.class}` are missing: " +
        missing_attrs.map { |a| "`#{a}`" }.join(', ')
    )
  end

  if invalid_attrs
    raise InvalidValue.new(
      "Some attributes of `#{self.class}` are invalid:\n" +
        invalid_attrs.map { |k,v| "  - #{k}: #{v.inspect}" }.join("\n") +
        "\n"
    )
  end
end

#inspectObject



148
149
150
151
152
153
154
# File 'lib/value_semantics/instance_methods.rb', line 148

def inspect
  attrs = to_h
    .map { |key, value| "#{key}=#{value.inspect}" }
    .join(" ")

  "#<#{self.class} #{attrs}>"
end

#pretty_print(pp) ⇒ Object



156
157
158
159
160
161
162
163
164
# File 'lib/value_semantics/instance_methods.rb', line 156

def pretty_print(pp)
  pp.object_group(self) do
    to_h.each do |attr, value|
      pp.breakable
      pp.text("#{attr}=")
      pp.pp(value)
    end
  end
end

#to_hHash



115
116
117
118
119
# File 'lib/value_semantics/instance_methods.rb', line 115

def to_h
  self.class.value_semantics.attributes
    .map { |attr| [attr.name, __send__(attr.name)] }
    .to_h
end

#with(new_attrs) ⇒ Object

Creates a copy of this object, with the given attributes changed (non-destructive update)



108
109
110
# File 'lib/value_semantics/instance_methods.rb', line 108

def with(new_attrs)
  self.class.new(to_h.merge(new_attrs))
end