Include vs Prepend no Ruby

Ultimamente tenho feito bastante uso dos módulos, principalmente dentro das minhas aplicações feitas usando o Rails. Hoje vou tentar explicar a diferença entre o include e o prepend.

Para entender as diferenças entre o include e prepend é necessário ter um entendimento substancial relativo a ascendência das classes em Ruby. Basicamente cada classe tem uma cadeia de ascendência. Vejamos um exemplo:

O que aconteceu?

No exemplo acima, quando chamamos B.new.bar, o ruby vai procurar em B,por um método de instância chamado bar, que não existe dentro dessa classe então, o Ruby passa a procurar na posição em cadeia, neste caso na class A . Daí assim que ele encontra o método bar o interpretador do Ruby para de procurar. Logo, o método bar é executado e o valor “bar” é retornado.

Quando chamamos B.new.foofooé procurado em B, neste caso, logo ele é encontrado, então, o Ruby para de procurar. No entanto, ao executar o fooda class B, o método super é chamado, e ele começa a procurar novamente pelo método foo, mas desta vez começando pelo pai na cadeia de ascendência deque neste caso é A. Como a classe contêm o método foo, então, o Ruby para de procurar. Logo após, foo da classe é invocado (retornando o valor “foo”) concatenando, neste caso a “bar”, logo, o método foo da class B retorna o valor “foobar”

Simples! né? 🙂

Include vs Prepend

Agora que a gente já viu como as coisas se dão, vamos ver um outro exemplo, dessa vez usando um módulo pra ver como include e prepend se comportam.

Eu procurei usar um contexto onde ficasse mais claro possível a diferenciação pra o uso do prepend e include, não estou levando consideração a aplicabilidade disso em uma situação real.

Include

Podemos ver neste exemplo como o Include se comporta. Ao invocarmos AccountUser.new.default_attributes o Ruby vai procurar na seguinte ordem: [AccountStore, Account…].
Como agente viu no exemplo passado, o Ruby primeiro procura o método dentro da classe do objeto, depois nos mixins, e por último na superclasse e seus mixins.
Dentro da classe AccountUser o Ruby vai encontrar este método e retornar {:cnpj => ""}.

Vale lembrar que os métodos criados dentro de módulos, assumem o escopo onde forem invocados.


Prepend

Usando este mesmo exemplo, agora vamos substituir o nosso include por prepend.

Coloquei aquele método ancestors ali de propósito, eu aposto que você já consegue entender o que o prepend faz 😛.

Agora já temos “um norte” de como as coisas aconteceram na chamada de AccountStore.new.default_attributes vai ficar mais fácil de explicar.

Como estamos fazendo uso do prepend o Ruby agora vai procurar primeiro em Account, nesta ocasião ele vai invocar o método super, que fazer um merge com common_attributes e nos retornar: {:cnpj=> "", :name=> "", :state=> "", :city=> ""}.

Conclusão

Provavelmente a diferença mais notável é a mudança na composição da cadeia de ascendência. Porém, como vimos, a forma como interpretador do Ruby é executado é o que faz com que obtenhamos o resultado esperado no uso desses métodos para o que desejamos fazer como eles.

Nota:
Use prepend quando quiser estender uma classe existente.
Use include para criar pequenos trechos de código que possam ser reutilizados em várias classes.

Abraço e até a próxima!