In Progress
Unit 1, Lesson 1
In Progress

# Equalizer

Video transcript & code

Over the last few weeks we've talked a lot about Value Objects. We've discussed how immutable value objects can reduce the possibility of mutation-related bugs in code. And we've looked at some libraries and gems that make it easier to build value objects.

Today let's build a value object to represent that classic computer science example, a point in 2D space.

We'll include Adamantium for immutability, as we saw in Episode #219.

There will be attributes: `x`, and `y`.

Of course we can initialize an object with `x` and `y` values.

Remember that in Ruby the equivalence operator takes into account only an object's identity by default. In order to compare two points for equivalence, we need to override the operator to use the object's state instead. Our version first checks the class of the other object, then compares x and y coordinates.

It seems like we should be done at this point. But as we learned in Episode #204, if there's a possibility we might use these objects as keys in a hash, or if we ever might store instances in a ruby `Set`, we need to define a `#hash` method that calculates using the object's value instead of its identity. We use an array to combine the hash values of x, y, and class into a single value.

We're almost done. But there's one more rule we need to satisfy in order to make this object behave correctly as a hash key or as an element in a set: along with the `#hash` method, we also need to define the hash equality, or `#eql?` method, in terms of the object's value. To do that, we alias it to the equivalence operator.

``````require "adamantium"

class Point

def initialize(x, y)
@x, @y = x, y
end

def ==(other)
other.is_a?(self.class) && x == other.x && y == other.y
end

def hash
[x, y, self.class].hash
end

alias_method :eql?, :==
end
``````

This seems like an awful lot of work to get a well-behaved value object. If we're going to be writing a lot of value object classes, we don't want to have to repeat similar boilerplate for the object's equality methods every time.

Enter the `equalizer` gem, by Dan Kubb. Like Adamantium, it is one of the spin-offs of the ROM project.

Let's include `Equalizer` into our class. The way we do this might be a little surprising: instead of just including a module, we include a generated module parameterized with the methods we want to use to determine equality. In our case, those are the `#x` and `#y` methods.

We then proceed to remove the equivalence operator, the `#hash` method, and the hash equality operator. The reason we remove these is that the `Equalizer` module generates them for us, based on the arguments names we passed to the module constructor.

``````require "adamantium"
require "equalizer"

class Point
include Equalizer.new(:x, :y)

def initialize(x, y)
@x, @y = x, y
end
end
``````

Let's check out what `Equalizer` has provided for us. First of all, let's experiment with seeing if two objects are equal or not.

``````require "./point2"

Point.new(23, 32) == Point.new(23, 32) # => true
Point.new(23, 32) == Point.new(23, 33) # => false
``````

Now let's try storing a `Point` in a `Set` container. Then we'll ask the container if it contains a `Point` with the same value. Remember from Episode #204, that using Ruby's default identity-based hashing semantics, this would return `false`.

``````require "./point2"
require "set"

s = Set.new
s << Point.new(4, 5)
s.include?(Point.new(4, 5))     # => true
``````

It returns `true`, showing that `Equalizer` has implemented value-based hashing for `Point` objects.

As a free gift, `Equalizer` gave us one more thing: a pretty `#inspect` method that neatly formats the values of the object's `x` and `y` attributes.

``````require "./point2"

puts Point.new(4, 5).inspect
# => nil
# >> #<Point x=4 y=5>
``````

In general Ruby is a pretty low-ceremony language. But even in Ruby there are cases where some boilerplate code needs to be repeated over and over. Defining equality semantics for value objects is one of those cases. Thankfully, Ruby also has sufficient metaprogramming power to enable the creation of libraries like Equalizer, that can do all that busywork for us.