Búsqueda de métodos en Ruby

Mario López

Yo diría que la herencia en Ruby es bastante fácil de entender. Cuando la definición de una clase es acompañada con un símbolo <, significa que hereda desde la otra clase. ¿Cierto?

class Animal
  def move_around
    "Just patrolling"
  end
end

class Cat < Animal
end

Al definirse que una clase hereda desde otra, es posible utilizar lo métodos de instancia de su ancestro.

Cat.new.move_around
=> "Just patrolling"

Ahora, ¿Cómo es que esto sucede? Bueno, lo que realmente sucede es que al llamar un método sobre algún objeto Ruby busca el método en la clase a la cual pertenece el objeto, y si no lo encuentra, comienza a buscar a lo largo de toda la cadena de ancestros del objeto.

Cat.ancestors
=> [Cat, Animal, Object, Kernel, BasicObject]

En Ruby la cadena de ancestros es revisada de izquierda a derecha, y al realizar herencia, lo que hacemos es agregar una clase al comienzo de esta cadena. Por lo tanto este es el motivo por el cual es posible sobre-escribir los métodos de una clase padre.

class Animal
  def move_around
    "Just patrolling"
  end 
end

class Cat
  def move_around
    "Walking on sunshine"
  end
end

Cat.new.move_around
=> "Walking on sunshine"

Pero esa no es toda la verdad acerca de la cadena de ancestros en Ruby. Todo objeto Ruby posee una singleton_class.

Una clase singleton es un patrón de diseño de la programación orientada a objetos que consiste implementar una clase capaz de tener una y solo una instancia.

Ruby define un método para acceder a la singleton_class de cualquier objeto (que por cierto, es un objeto de la clase Class😱).

Cat.new.singleton_class
=> #<Class:#<Cat:0x007ff2ec26fe80>>

Esta clase singleton nos permite modificar los métodos de instancia del objeto sin pasar a llevar a las otras instancias de la clase.

tiger = Cat.new
puma = Cat.new

puma.instance_eval do 
  def move_around
    "I'm going for a walk"
  end
end

tiger.move_around
=> "Walking on sunshine"

puma.move_around
=> "I'm going for a walk"

Y por lo tanto, ahora si podemos ver la cadena de ancestros completa que es utilizada por Ruby para buscar los métodos a la hora de llamar un método de instancia.

Cat.new.singleton_class.ancestors
=> [#<Class:#<Cat:0x007f98ca02d360>>, Cat, Animal, Object, Kernel, BasicObject]

Primero busca dentro de su singleton_class, luego busca en su clase, luego en su clase padre y finalmente en el resto de los ancestros.