We’ll start with a cow named cletus
. Cows are ruminant animals. And ruminants are all part of the broader family of ungulates . Today’s topic isn’t actually about mammal taxonomy, though. Our real focus is on how objects find their methods. So we’re going to write a special method to help us with this task. We call it where
, and it will take an object and a method name as arguments. Inside, it looks through the object’s class ancestors until it finds the nearest ancestor to defined the given method. With our tools defined, let’s put Cletus through his paces. Cletus can speak, and when he does, he moos. Cletus get this behavior from the fact that he is a Cow
. When Cletus rests, he chews his cud. That’s because cows are Ruminant mammals. And when he eats, he grazes. This is fairly typical of ungulates, a group that also includes horses, deer, and giraffes.
class Ungulate
def eat
"graze graze graze"
end
end
class Ruminant < Ungulate
def rest
"chew chew chew"
end
end
class Cow < Ruminant
def speak
"moo"
end
end
require "./classes"
def where(obj, method)
obj.singleton_class.ancestors.detect do |klass|
klass.instance_methods(false).include?(method)
end
end
cletus = Cow.new
cletus.speak # => "moo"
where(cletus, :speak) # => Cow
cletus.rest # => "chew chew chew"
where(cletus, :rest) # => Ruminant
cletus.eat # => "graze graze graze"
where(cletus, :eat) # => Ungulate
def cletus.dance
"tappety tappety tap"
end
cletus.dance
# => "tappety tappety tap"
where(cletus, :dance) # => #<Class:#<Cow:0x0055c1fb9c05c8>>
Cow.new.singleton_class.ancestors
# => [#<Class:#<Cow:0x0055978f993a78>>,
# Cow,
# Ruminant,
# Ungulate,
# Object,
# PP::ObjectMixin,
# Kernel,
# BasicObject]
In Ruby, method lookup follows a simple and consistent rule. An object’s method definitions are found via its class inheritance chain. Always. In order for objects to be able to have individual method definitions, they have a singleton class that’s unique to them, and that singleton class is the first place Ruby looks for a method definition.