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:

Retirei este exemplo deste post.

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.foo, fooé procurado em B, neste caso, logo ele é encontrado, então, o Ruby para de procurar. No entanto, ao executar o foo da 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 deB que 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 A é 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 :P.

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.

Aproveitando, dá uma passada no site Vídeos de TI, pra ter acesso uma gama de conteúdos legais! Também não esqueçam de deixar seus comentários, nos seguir nas redes sociais e também se cadastrar em nossa newsletter semanal.

Referências:

https://blog.daveallie.com/include-vs-prepend
https://ruby-doc.org/core-2.5.1/Module.html
https://ruby-doc.org/core-2.5.1/Module.html#method-i-prepend

Abraço e até a próxima.