Code & Clay Notes to self. Mainly Ruby/Rails.

Override `#hash` when overriding `#eql?`

In my notes here, I’ve written that #hash must be overridden when overriding #eql?. I was unsure why this was the case.

What is #hash?

I see in pry that all objects respond to #hash:

> 1.hash
=> 3748939910403886956
> 'a'.hash
=> 1677925148165319732
> [1,2,3].hash
=> -2230614089907012012
> Time.now.hash
=> -2249667312364590389

Equal objects return the same value:

> 1 == 1
=> true
> 1.hash
=> 3748939910403886956
> 1.hash
=> 3748939910403886956

Hash comes from the Kernel module:

> 1.method(:hash).owner
=> Kernel

The Kernel module is included by class Object, so its methods are available in every Ruby object.

It’s basically a bunch of helper methods made available to every class.

Kernel#hash seems to be undocumented though. It’s not in in the docs for the Kernel module. Though, that page does point towards the Object class page for information on Kernel instance methods. But there’s nothing on it there either.

The explanation to why #hash needs to be overridden when #eql is changed is in the Hash docs.

Two objects refer to the same hash key when their hash value is identical and the two objects are eql? to each other.

Two objects can have the same hash value but be unequal however this would be detriment to the speed of the hash.

So when keying by multiple objects, so long as their hash and eql? values match, they will point to the same bucket.

The naked asterisk

Andrew Berls writes about the naked asterisk in this post. I too stumbled across it whilst looking through Rails source.

Andrew says:

So what’s the deal with the unnamed asterisk? Well, this is still the same splat operator at work, the difference being that it does not name the argument array. This may not seem useful, but it actually comes into play when you’re not referring to the arguments inside the function, and instead calling super. To experiment, I put together this quick snippet:

class Bar
  def save(*args)
    puts "Args passed to superclass Bar#save: #{args.inspect}"
  end
end

class Foo < Bar
  def save(*)
    puts "Entered Foo#save"
    super
  end
end

Foo.new.save("one", "two")

which will print out the following:

Entered Foo#save
Args passed to superclass Bar#save: ["one", "two"]

The globbed arguments are automatically passed up to the superclass method. Some might be of the opinion that it’s better to be explicit and define methods with def method(*args), but in any case it’s a neat trick worth knowing!

Value objects

A small simple object, like money or a date range, whose equality isn’t based on identity. Martin Fowler

Symbol, String, Integer and Range are example of value objects.

Value Objects in Ruby on Rails

Notes to this presentation.

March is not 3. 3 is not March. March is March.

March is a range of days, 1st – 31st.

Examples of value objects:

  • Date, Time
  • Weight, Height
  • Duration
  • Temperature
  • Address
  • Money

A value object has:

  • Identity based on state
    • a dollar is a dollar
    • Nov 5 is Nov 5
    • 98.6F is 98.6F
    • 10 Downing Street is 10 Downing Street
  • Equality based on type and state
    • 98.6F != $98.60

They are immutable – or should be treated as such – and typically do not have setters.

Scalar values example

class Patient
  attr_accessor :height, :weight
end

patient.height #=> 65
patient.weight #=> 90

What do these values represent? What are their units?

class Height
  def initialize(inches)
    @inches = inches
  end

  def inches
    @inches
  end

  def centimetres
    inches * 2.54
  end
end

class Weight
  def initialize(pounds)
    @pounds = pounds
  end

  def pounds
    @pounds
  end

  def kilograms
    pounds / 2.2
  end
end

class Patient
  attr_accessor :height_inches, :weight_pounds

  def height
    Height.new(@height_inches)
  end

  def weight
    Weight.new(@weight_pounds)
  end
end

patient.weight.pounds #=> 100
patient.weight.kilograms #=> 45.4545454545

patient.height.inches #=> 65
patient.height.centimetres #=> 165.1

Equality

a = Weight.new(100)
b = Weight.new(110)
c = Weight.new(100)

a == b #=> false
a == c #=> false

a and c are not equal because they are different objects.

class Weight
  # ...

  def hash
    pounds.hash
  end

  def eql?(other)
    self.class == other.class &&
      self.pounds == other.pounds
  end
  alias :== eql?
end

We need to override #hash when we override #==.

The weight class now looks like:

  attr_reader :pounds

  def initialize(pounds)
    @pounds = pounds
  end

  def kilograms
    pounds / 2.2
  end

  def hash
    pounds.hash
  end

  def eql?(other)
    self.class == other.class &&
      self.pounds == other.pounds
  end
  alias :== eql?
end

Refactor to a struct

Weight = Struct.new(:pounds) do
  def kilograms
    pounts/2.2
  end
end

a = Weight.new(100)
b = Weight.new(110)
c = Weight.new(100)

a == b #=> false
a == c #=> true

But structs are mutable.

Value object gem

You can use value_object gem.

class Timespan < ValueObject::Base
  has_fields :start_time, :end_time

  def duration
    Duration.new(end_time - start_time)
  end

  def contains?(time)
    (start_time..end_time).cover?(time)
  end

  def overlays?(other)
    contains?(other.start_time) || contains?(other.end_time)
  end
end