Sonntag, 26. April 2015

Compare Ruby objects with Threequals!

Apart from simple comparision with == or eql? there is a another one: Object#=== aka Threequal.
Its intention is to provide "Case Equality". The Documention furthermore states:

For class Object, effectively the same as calling #==, but typically overridden by descendants to provide meaningful semantics in case statements.

It is meant to to be overwritten!
In some classes it is already achieved. For example Range#===:
(1..5) === 3
=> true
So there is a comparision going on between two kind of objects, a Range and a Fixnum. Briefly the Threequal "operator" technically is nothing more than a method expecting a parameter, similar to == or +.
But this one is special. The Threequal also is called when a case statement tries to compare. The Range class once again:
case 24
when 0..18
  'Underweight'
when 18..25
  'Normal weight'
else
  'Adiposity (overweight)'
end
=> 'Normal weight'
...Phew.
Please note, the Threequal has to be overwritten in the when-branches-object class, not in the one the case statement tries to compare.
So whenever semantic comparision is required overwriting Object#=== totally makes sense. Semantic comparision is emphasized. A Ruby On Rails example:
But setting up the models first:
rails g model Category name:string
rails g model Language name:string category_id:integer
class Category  < ActiveRecord::Base
  def self.[] name
    where(name: name).first
  end
 
  def === language
    language.category == self
  end
end

class Language < ActiveRecord::Base
  belongs_to :category
end
A language can belong to a category. The catogory Threequal method compares the assigned language category with itself. The class method Category#[] finds the corresponding object.
Creating a dynamic language:
ruby = Language.create name: 'Ruby', 
  category: Category.dynamic
and comparing it somewhere else:
case ruby
  when Category[:dynamic] then 'Awesome!'
  when Category[:static] then 'Huh.'
  else 'Anyway.'
end
=> "Awesome!"
returns the right answer.

Supported by Ruby 2.2.1 and Ruby on Rails 4.2.0