La mejor forma en que entiendo los mixins es como clases virtuales. Los mixins son "clases virtuales" que se han inyectado en la cadena de ancestros de una clase o módulo.
Cuando usamos "include" y le pasamos un módulo, agrega el módulo a la cadena de antecesores justo antes de la clase de la que heredamos:
class Parent
end
module M
end
class Child < Parent
include M
end
Child.ancestors
=> [Child, M, Parent, Object ...
Cada objeto en Ruby también tiene una clase singleton. Los métodos agregados a esta clase singleton se pueden invocar directamente en el objeto, por lo que actúan como métodos de "clase". Cuando usamos "extender" en un objeto y pasamos el objeto a un módulo, estamos agregando los métodos del módulo a la clase singleton del objeto:
module M
def m
puts 'm'
end
end
class Test
end
Test.extend M
Test.m
Podemos acceder a la clase singleton con el método singleton_class:
Test.singleton_class.ancestors
=> [#<Class:Test>, M, #<Class:Object>, ...
Ruby proporciona algunos ganchos para los módulos cuando se mezclan en clases / módulos. included
es un método de enlace proporcionado por Ruby que se llama cada vez que incluye un módulo en algún módulo o clase. Al igual que se incluye, hay un extended
gancho asociado para extender. Se llamará cuando un módulo sea extendido por otro módulo o clase.
module M
def self.included(target)
puts "included into #{target}"
end
def self.extended(target)
puts "extended into #{target}"
end
end
class MyClass
include M
end
class MyClass2
extend M
end
Esto crea un patrón interesante que los desarrolladores podrían usar:
module M
def self.included(target)
target.send(:include, InstanceMethods)
target.extend ClassMethods
target.class_eval do
a_class_method
end
end
module InstanceMethods
def an_instance_method
end
end
module ClassMethods
def a_class_method
puts "a_class_method called"
end
end
end
class MyClass
include M
# a_class_method called
end
Como puede ver, este módulo único está agregando métodos de instancia, métodos de "clase" y actuando directamente sobre la clase de destino (en este caso, llamando a a_class_method ()).
ActiveSupport :: Preocupación encapsula este patrón. Aquí está el mismo módulo reescrito para usar ActiveSupport :: Preocupación:
module M
extend ActiveSupport::Concern
included do
a_class_method
end
def an_instance_method
end
module ClassMethods
def a_class_method
puts "a_class_method called"
end
end
end