Cuando incluimos un módulo en una clase creamos un "mixin", es decir, incluimos todos los métodos del módulo en dicha clase.
Por ejemplo:
module Validable
def valid?
true
end
end
class Ejemplo1
include Validable
end
>> Ejemplo1.ancestors
=> [Ejemplo1, Validable, Object, Wirble::Shortcuts, PP::ObjectMixin, Kernel]
>> Ejemplo1.new.valid?
=> true
Ahora la clase Ejemplo tiene los métodos de Validable.
Con el método ancestors, de la clase Module podemos ver los módulos incluidos en un módulo o una clase. Además nos muestra el orden de busqueda de los módulos.
En nuestro caso al invorcar el método valid? sobre una instancia del objeto, el interprete primero buscará en la definicion de la clase Ejemplo, como no lo encontrará buscará en el siguiente módulo de su lista de ancestors, que es Validable y que sí que tiene el método definido.
Por tanto si definimos el método valid? en la clase Ejemplo, ejecutará este método en vez del valid? del metodo Validable:
class Ejemplo2
include Validable
def valid?
false
end
end
>> Ejemplo2.ancestors
=> [Ejemplo2, Validable, Object, Wirble::Shortcuts, PP::ObjectMixin, Kernel]
>> Ejemplo2.new.valid?
=> false
Puede ocurrir que el módulo que incluimos dependa de métodos propios para realizar una tarea y que nuestra clase contenga el mismo método definido y entre en conflicto, por ejemplo:
module Validable
def valid?
check
end
def check
true
end
end
class Ejemplo3
include Validable
def check
if valid?
"Valid"
else
"Invalid"
end
end
end
>> Ejemplo3.new.check
SystemStackError: stack level too deep
from (irb):84:in `check'
from (irb):72:in `valid?'
from (irb):84:in `check'
...
Lo que pasa es que llamamos al método check sobre una instancia de Ejemplo3. El interprete recorre la lista de ancestors y encuentra que la clase Ejemplo3 tiene un metodo check definido y lo invoca.
El método check llama al método valid? y se produce la misma busqueda anterior. Comprueba el primer módulo/clase en la lista de ancestors.
Ejemplo3 no tiene el método valid? definido. El módulo Validable sí lo tiene, por tanto lo llama.
A su vez, el método valid? del módulo Validable llama a un método tambien definido en Validable, pero el interprete no lo ejecuta directamente, sino que repite el proceso de busqueda del método por toda la lista de ancestors y, como Ejemplo3 también lo tiene definido y es el primero de la lista, ejecutará el metodo check de Ejemplo3 y no el de Validable como nosotros queremos.
Se produce por tanto un bucle que interprete acaba abortando.
¿Como solucionar este problema? La mejor solución es cambiar el nombre de los métodos del módulo o de la clase para evitar conflictos, pero a veces esto no es posible, bien porque el módulo pertenezca a una libreria externa, o bien porque la aplicación está en producción y ya hay partes de la misma que dependen del método.
Para solvertar el problema, una solución es usar una clase auxiliar que incluya el módulo Validable y así nos proporcione un proxy a la funcionalidad del módulo:
module Validable
def valid?
check
end
def check
true
end
end
class Ejemplo4
# clase auxiliar que incluye la funcionalidad de Validable
class ValidableHelper
include Validable
end
def check
if ValidableHelper.new.valid?
"Valid"
else
"Invalid"
end
end
end
>> Ejemplo4.new.check
=> "Valid"
La clase que include la funcionalidad del método es ValidableHelper. Invocando el método valid? sobre una nueva instancia de esta clase tendremos en comportamiento deseado.
