A melhor maneira de entender mixins é como classes virtuais. Mixins são "classes virtuais" que foram injetadas na cadeia ancestral de uma classe ou módulo.
Quando usamos "include" e passamos a ele um módulo, ele adiciona o módulo à cadeia ancestral logo antes da classe da qual estamos herdando:
class Parent
end
module M
end
class Child < Parent
include M
end
Child.ancestors
=> [Child, M, Parent, Object ...
Todo objeto no Ruby também tem uma classe singleton. Os métodos adicionados a essa classe singleton podem ser chamados diretamente no objeto e, portanto, agem como métodos de "classe". Quando usamos "estender" em um objeto e passamos ao objeto um módulo, estamos adicionando os métodos do módulo à classe singleton do objeto:
module M
def m
puts 'm'
end
end
class Test
end
Test.extend M
Test.m
Podemos acessar a classe singleton com o método singleton_class:
Test.singleton_class.ancestors
=> [#<Class:Test>, M, #<Class:Object>, ...
O Ruby fornece alguns ganchos para os módulos quando eles estão sendo misturados em classes / módulos. included
é um método de gancho fornecido pelo Ruby que é chamado sempre que você inclui um módulo em algum módulo ou classe. Assim como incluído, há um extended
gancho associado para extensão. Será chamado quando um módulo for estendido por outro módulo ou classe.
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
Isso cria um padrão interessante que os desenvolvedores poderiam 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 você pode ver, este módulo único está adicionando métodos de instância, métodos "class" e agindo diretamente na classe de destino (chamando a_class_method () neste caso).
ActiveSupport :: Concern encapsula esse padrão. Aqui está o mesmo módulo reescrito para usar o ActiveSupport :: Concern:
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