<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Stephann</title>
    <description>The latest articles on DEV Community by Stephann (@stephann).</description>
    <link>https://dev.to/stephann</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F219640%2F2f6dd8e9-f113-4756-906f-5a280efd8cb9.png</url>
      <title>DEV Community: Stephann</title>
      <link>https://dev.to/stephann</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/stephann"/>
    <language>en</language>
    <item>
      <title>A maravilha dos pull requests pequenos</title>
      <dc:creator>Stephann</dc:creator>
      <pubDate>Tue, 09 Jan 2024 13:03:39 +0000</pubDate>
      <link>https://dev.to/stephann/a-maravilha-dos-pull-requests-pequenos-370f</link>
      <guid>https://dev.to/stephann/a-maravilha-dos-pull-requests-pequenos-370f</guid>
      <description>&lt;p&gt;Uma funcionalidade nova é solicitada e é passada ao time de desenvolvimento, uma pessoa pega essa atividade e começa a trabalhar nela. Nas primeiras das famigeradas daily meetings/standup meetings são reportados os avanços esperados: “Ontem criei os modelos e hoje vou fazer a tela de listagem” ou “Ontem implementei os testes da tela de cadastro e hoje vou integrar o novo editor de texto”. Passam duas semanas, três semanas, um mês, ou até meses (sim, meses) e os relatórios diários se tornam repetitivos, algo como “Ontem eu continuei investigando o problema no editor, e hoje vou continuar nessa investigação, mas tá perto, falta só isso e aquilo, acho que próxima semana termino”. Essa próxima semana nunca chega e a gestão vive perguntando no canal de comunicação: “Como tá essa atividade? Tem algum prazo?”. Finalmente quando a atividade é finalizada, é aberto um pull request/merge request de dezenas (até centenas) de arquivos, com as modificações mais abrangentes possíveis misturando novas funcionalidades, refatorações e correções. A revisão de código se arrasta por mais alguns dias ou semanas, e em muitos casos só é aprovado porque daria um trabalho imenso apontar onde e como melhorar e também ficaria feio falar “Joga fora e faz de novo totalmente diferente”, que talvez fosse até a melhor opção.&lt;/p&gt;

&lt;p&gt;Já presenciei as cenas narradas acima algumas vezes e acredito que a maioria de nós que trabalha com desenvolvimento de software já passou por uma situação similar em algum momento da carreira ou ainda vai passar, seja como a pessoa que desenvolve, seja como colega de time, líder dessa pessoa, gerente de projeto, scrum master e etc. Os motivos pra situações como essa acontecerem podem ser vários, pode ser que a tarefa era ambiciosa demais, que a pessoa não tinha capacidade técnica pra executá-la, que a pessoa estava assistindo vídeo fingindo que estava trabalhando, a pessoa estava passando por problemas, que o escopo não era claro e mudava muito durante a implementação, que as prioridades mudavam constantemente, a base de código estava deteriorada, enfim, há inúmeras possíveis causas.&lt;/p&gt;

&lt;p&gt;Pra evitar passar por mais situações como essa, eu venho incentivando os times em que trabalho a encararem as atividades recebidas como projetos e quebrar ao máximo essas atividades em atividades menores. Apesar do título do artigo falar em pull requests, isso vai além, é toda uma cultura de trabalho, é um processo de entrega que envolve o time de produto e tecnologia, e que traz benefícios amplos. A adoção de pequenas tarefas tem se provada uma ótima decisão pra todos os envolvidos e explico meus pontos a seguir.&lt;/p&gt;

&lt;h4&gt;
  
  
  Revisão de código mais eficiente
&lt;/h4&gt;

&lt;p&gt;Com pull requests com menos arquivos, e com mudanças mais focadas, as revisões ficam mais rápidas e, principalmente, ganham qualidade, pois é mais fácil entender as mudanças e sugerir novas ideias. Em um PR muito grande, a qualidade exigida durante a revisão de código cai bastante. Há um ditado que fala: A qualidade de uma revisão é inversamente proporcional ao tamanho do pull request.&lt;/p&gt;

&lt;h4&gt;
  
  
  Facilidade no QA/Homologação
&lt;/h4&gt;

&lt;p&gt;Seguindo a mesma ideia do ponto anterior, uma mudança menor é mais fácil de ser testada e homologada, e caso algum erro apareça, mais rápido o erro será corrigido.&lt;/p&gt;

&lt;h4&gt;
  
  
  Facilidade no deploy/rollback
&lt;/h4&gt;

&lt;p&gt;Uma mudança menor também tem menos chances de causar problema, por alterar menos arquivos, por ter passado por uma revisão mais criteriosa de código e de funcionamento, e caso aconteça algum problema, na maioria dos casos é mais fácil reverter a mudança.&lt;/p&gt;

&lt;h4&gt;
  
  
  Menos tempo com PR aberto
&lt;/h4&gt;

&lt;p&gt;Com as revisões sendo mais rápidas, as mudanças têm menos chances de terem conflitos com as mudanças de outras atividades, e a pessoa que está executando tem menos troca de contexto. Quando uma pessoa tem vários outros PRs abertos, ela vai precisar ficar indo e voltando neles durante vários dias para corrigir comentários, conflitos, erros de homologação e etc.&lt;/p&gt;

&lt;h4&gt;
  
  
  Melhor planejamento
&lt;/h4&gt;

&lt;p&gt;Para quebrar um projeto em atividades menores, a pessoa vai precisar raciocinar e ter pelo menos uma visão mais ampla do que será necessário fazer para entregar aquele objetivo, levando a um planejamento prévio antes de sair escrevendo código.&lt;/p&gt;

&lt;h4&gt;
  
  
  Melhora ritmo de entrega
&lt;/h4&gt;

&lt;p&gt;A pessoa quando está executando uma atividade muito longa, ela tem maior facilidade de perder o ritmo e a produtividade, pois ela está afundada em vários arquivos que ela modificou há dias, sem saber fácil o que foi feito e o que falta fazer. Com atividades pequenas, todo dia a pessoa está iniciando e entregando uma, duas, três atividades ou até mais.&lt;/p&gt;

&lt;h4&gt;
  
  
  Facilita o acompanhamento do progresso
&lt;/h4&gt;

&lt;p&gt;A gerência não precisa ficar mais jogando mensagem no canal de comunicação: "Como tá essa atividade?", "Conseguimos entregar essa semana?". Aliando esse melhor planejamento com um quadro de tarefas atualizado, é fácil acompanhar como está o progresso de uma entrega, pois dará para ver que, por exemplo, das 9 atividades, 6 já foram concluídas e que outras duas estão em andamento.&lt;/p&gt;

&lt;h4&gt;
  
  
  Facilita detectar problemas individuais
&lt;/h4&gt;

&lt;p&gt;Nem todo mundo está 100% em 100% do tempo, há períodos de baixa e é necessário entender isso. Adotando as atividades pequenas, é fácil entender quando uma pessoa do time está repetidamente demorando várias semanas para entregar duas ou três atividades, enquanto o resto do time entrega 5 a 10 atividades por semana. Isso ajuda a liderança detectar situações assim e iniciar conversas para entender o que a pessoa está passando e ter uma alinhamento mais claro.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mas como dividir melhor as atividades?
&lt;/h2&gt;

&lt;p&gt;Primeiro é importante entender o que é necessário pra colocar isso em prática e eu penso que há 4 pontos:&lt;/p&gt;

&lt;h4&gt;
  
  
  Capacidade de planejamento
&lt;/h4&gt;

&lt;p&gt;A pessoa que vai executar a tarefa, precisa saber dividir, ordenar e priorizar bem as atividades. Essa habilidade é possível aprender praticando no dia a dia e com ajuda do time que vai revisar suas entregas. A própria pessoa também vai saber quando um pull request ficou grande e tentará melhorará na próxima oportunidade. Também é possível durante a execução de uma atividade, perceber que algo ficou muito extenso, dividir o que foi feito em duas ou mais atividades e criar pull requests pra cada uma e isso servirá de aprendizado para os próximos planejamentos.&lt;/p&gt;

&lt;h4&gt;
  
  
  Boa estratégia de deploy
&lt;/h4&gt;

&lt;p&gt;Adotando essa estratégia, muitas vezes uma atividade não estará completa a ponto de ser disponbilizada aos usuários, então é necessário escondê-la e tomar cuidado para ela não impactar as coisas que estão funcionando corretamente. Isso pode ser feito com a estratégia de feature flag, por exemplo. Também é importante termos um deploy pra produção e staging facilitado, pois aumenta o volume de entregas, e qualquer atrito nesse processo pode não dar os resultados esperados.&lt;/p&gt;

&lt;h4&gt;
  
  
  Contornar gargalos
&lt;/h4&gt;

&lt;p&gt;Como pode acontecer de uma atividade depender da outra, por exemplo: A atividade de listagem pode depender da atividade de criação do modelo, é importante saber sinalizar e contornar essas dependências, seja tendo outras atividades na fila pra serem iniciadas, seja priorizando a entrega da atividade dependente.&lt;/p&gt;

&lt;h4&gt;
  
  
  Alinhamento com as pessoas de produto
&lt;/h4&gt;

&lt;p&gt;O time de produto precisa estar alinhado com essa estratégia, sabendo que nem toda atividade entregará valor ao usuário final. Também aproveitar que a atividade está bem dividida pra colocar em prática a redução de escopo quando os prazos não forem possíveis de ser alcançados.&lt;/p&gt;

&lt;h2&gt;
  
  
  Exemplo
&lt;/h2&gt;

&lt;p&gt;Agora vou mostrar um exemplo de como gosto de fazer o planejamento de uma atividade/projeto. Vamos supor que eu preciso entregar a funcionalidade de permitir que o usuário publique uma newsletter para os seus seguidores, os requisitos são:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;O usuário pode listar, criar, editar e apagar posts&lt;/li&gt;
&lt;li&gt;O usuário pode editar o corpo do post com um editor de texto avançado&lt;/li&gt;
&lt;li&gt;O usuário pode salvar um post como rascunho&lt;/li&gt;
&lt;li&gt;As modificações feitas no corpo devem ser salvas automaticamente&lt;/li&gt;
&lt;li&gt;O post deve ser enviado aos seguidores do usuário por e-mail&lt;/li&gt;
&lt;li&gt;O post deve estar visível publicamente na página do usuário&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Geralmente eu vejo as pessoas fazendo isso em uma, duas, três atividades, mas eu dividiria isso em pelo menos umas 20 atividades, sem exagero. Resumindo um pouco, ficaria algo mais ou menos assim:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Criar modelo Post &lt;/li&gt;
&lt;li&gt;Criar componente do design system para ser usado na listagem de posts &lt;/li&gt;
&lt;li&gt;Criar tela de listagem de posts&lt;/li&gt;
&lt;li&gt;Criar tela de cadastro de post (com um textarea simples para o corpo)&lt;/li&gt;
&lt;li&gt;Criar tela de edição de post&lt;/li&gt;
&lt;li&gt;Permitir apagar um post&lt;/li&gt;
&lt;li&gt;Enviar post para os seguidores após a criação&lt;/li&gt;
&lt;li&gt;Criar tela pública de listagem de post&lt;/li&gt;
&lt;li&gt;Permitir que um post possa ser salvo como rascunho&lt;/li&gt;
&lt;li&gt;Aplicar editor de texto avançado na criação/edição de post&lt;/li&gt;
&lt;li&gt;Permitir upload de imagens no editor de texto avançado&lt;/li&gt;
&lt;li&gt;Configurar salvamento automático no editor de texto&lt;/li&gt;
&lt;li&gt;etc..&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Perceba que com essas atividades bem divididas, eu já consigo entregar a funcionalidade pro usuário com as 8 primeiras atividades finalizadas, facilitando a diminuição do escopo de entrega e entregando valor mais rápido, permitindo validar a ideia e iterar em cima dela com as demais atividades. E voltando pros pull requests, vai ser melhor revisar a criação do modelo, com suas validações e relacionamentos, do que revisar essa mesma coisa mas junto e misturado com 4, 5 telas, controllers, testes e etc.&lt;/p&gt;

&lt;p&gt;E uma forma que não recomendo fazer, que já vi dividirem as atividades assim e &lt;strong&gt;NÃO&lt;/strong&gt; funcionou porque não conseguiu tirar os proveitos listados nesse texto:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Implementar modelos&lt;/li&gt;
&lt;li&gt;Implementar use cases&lt;/li&gt;
&lt;li&gt;Implementar controllers&lt;/li&gt;
&lt;li&gt;Implementar páginas&lt;/li&gt;
&lt;li&gt;Implementar testes&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Conclusão
&lt;/h2&gt;

&lt;p&gt;Adotar essa divisão de atividades em atividades bem menores é uma mudança que no começo é difícil, por ainda estar em fase de adaptação, não ter certeza se está dividindo as atividades da forma certa, se as atividades estão grandes ou pequenas demais, mas com o tempo essa prática ficará confortável e trará os benefícios esperados.&lt;/p&gt;

&lt;p&gt;Também é necessário uma mudança de mentalidade de todos que fazem parte do processo de entrega, não adianta quebrar as atividades em atividades menores, e essas atividades demorarem uma vida pra serem revisadas, aprovadas e integradas, pois ao invés de facilitar, só criará gargalos no processo.&lt;/p&gt;

&lt;p&gt;Mesmo que no fim, o tempo de entrega não se altere, ou até aumente um pouco, essa prática não é apenas sobre isso, tem toda uma questão de manter-se num ritmo contínuo de entrega, que em atividades longas, esse ritmo é muito mais fácil quebrar, mais difícil de detectar que quebrou e entender as causas.&lt;/p&gt;

&lt;p&gt;E claro, isso é só um relato das minhas observações do que funcionou pra mim, mas pra você algo não pode funcionar, então é bom ficar atento pra fazer os ajustes necessários pra se adaptar ao seu contexto.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Seus testes não deveriam ser DRY, mas sim WET</title>
      <dc:creator>Stephann</dc:creator>
      <pubDate>Tue, 26 Sep 2023 16:59:09 +0000</pubDate>
      <link>https://dev.to/stephann/seus-testes-nao-deveriam-ser-dry-mas-sim-wet-4hch</link>
      <guid>https://dev.to/stephann/seus-testes-nao-deveriam-ser-dry-mas-sim-wet-4hch</guid>
      <description>&lt;p&gt;Bom, primeiro é bom deixar claro que o que vou escrever aqui não é nada novo, mas como comecei a aplicar esses conceitos nos últimos 2 anos nos projetos em que trabalhei e vi que tem sido realmente bem mais vantajoso do que a forma que eu fazia até então, resolvi escrever sobre. Se quiser ler/assistir outras pessoas falando sobre o assunto, seguem os links:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/live/BeysighX6jY?si=vbGnbnzMOLmNoeMV&amp;amp;t=495"&gt;Story Telling Testing: Conte uma historia com seus testes - Matheus Sales&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/thoughtbot/guides/blob/main/testing-rspec/avoid_let_spec.rb"&gt;Thoughtbot Guides&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://thoughtbot.com/blog/my-issues-with-let"&gt;My issues with let&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://thoughtbot.com/blog/lets-not"&gt;Let's not&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  DRY x WET
&lt;/h2&gt;

&lt;p&gt;DRY e WET são mais uns desses acrônimos que adoram colocar em princípios de desenvolvimento. DRY corresponde a Don't Repeat Yourself, traduzindo seria: Não se repita, que prega que você deve evitar ficar replicando um mesmo código em vários lugares da sua aplicação. Já o WET é o inverso disso, descreve códigos repetidos e pode ser traduzido como "Nós amamos digitar" (We Enjoy Typing) ou "Escreva tudo duas vezes/três vezes" (Write Everything Twice/Thrice). &lt;/p&gt;

&lt;p&gt;O consenso é que DRY é bom, e WET é ruím. Isso prega na sua cabeça principalmente quando você está saindo do nível júnior e indo para o nível pleno. Não é incomum encontrar aqueles conteúdos "Entenda o conceito do SOLID e deixe de ser Jr. e suba de nível", e fala superficialmente que não repetir código é uma das habilidades necessárias para ser uma boa pessoa desenvolvedora. &lt;/p&gt;

&lt;p&gt;Gosto de pensar que Júnior é o nível onde a pessoa aprende a desenvolver e consegue aplicar esses conhecimentos em aplicações reais, já o Pleno é o nível onde você entende e aplica de forma errada todos os princípios e boas práticas que colocam na sua frente, e você começa a se tornar senior quando vai aprendendo em quais contextos esses princípios são benéficos ou fazem mais mal do que bem.&lt;/p&gt;

&lt;h2&gt;
  
  
  O RSpec
&lt;/h2&gt;

&lt;p&gt;Como o consenso é que DRY é bom, acabamos aplicando essa prática em todo o código, inclusive nos nossos testes, e o RSpec, uma das principais ferramentas de testes utilizadas pela comunidade Ruby, ajuda a perpetuar essa prática. Ele fornece vários mecanismos para secar seu código, evitando duplicações, alguns desses mecanismos são os &lt;code&gt;#let&lt;/code&gt;, &lt;code&gt;#subject&lt;/code&gt;, &lt;code&gt;#before&lt;/code&gt;, &lt;code&gt;#around&lt;/code&gt;, &lt;code&gt;#after&lt;/code&gt; e os shared examples. Um exemplo de código:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;
&lt;span class="n"&gt;describe&lt;/span&gt; &lt;span class="no"&gt;Foo&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;describe&lt;/span&gt; &lt;span class="s2"&gt;"#bar"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:foo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="no"&gt;Foo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;z&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;let&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;let&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;let&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:z&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;let&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:calc_result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;before&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;allow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Calculator&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;receive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:calc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;z&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;and_return&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;calc_result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="s2"&gt;"when x is greater than y"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;let&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="n"&gt;let&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"returns something nice"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bar&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="s2"&gt;"when y is greater than x"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"returns something great"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bar&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="s2"&gt;"when calc result returns false"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;let&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:calc_result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"..."&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bar&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="s2"&gt;"when calculator raises error"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;before&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="n"&gt;allow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Calculator&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;receive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:calc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;z&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;and_raise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Some error"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;

      &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"..."&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Esse código está seguindo as boas práticas do DRY, e provavelmente se você não se incomodou com ele é porque você já deve estar acostumado a escrever código assim e ou acha que esse é a única forma de escrever testes com o RSpec. &lt;/p&gt;

&lt;p&gt;Eu acredito que é um código problemático a longo prazo, pois você olha para um exemplo de teste (o &lt;code&gt;it&lt;/code&gt;) e não consegue ver claramente como aquele teste está configurado, pois tem dependências no subject, nas variáveis &lt;code&gt;let&lt;/code&gt;, nos &lt;code&gt;before&lt;/code&gt;, e tudo isso pode ter sido ou não sobrescrito num escopo de &lt;code&gt;context&lt;/code&gt; ou &lt;code&gt;describe&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;Então basicamente você precisa mapear mentalmente todo o arquivo de teste para entender como um teste está funcionando. Além disso, se você precisar modificar/adicionar um cenário, você sempre estará numa batalha entre adaptar as declarações existentes e quebrar os demais testes sem relação com sua atividade, e/ou acabar tendo que criar mais &lt;code&gt;let&lt;/code&gt;/&lt;code&gt;subject&lt;/code&gt;/&lt;code&gt;before&lt;/code&gt;. Então o DRY nessa situação me parece uma furada.&lt;/p&gt;

&lt;p&gt;Mas apesar de tudo, não é o RSpec o vilão, podemos aproveitar de vários benefícios da ferramenta mas sem utilizar as que consideramos problemáticas. Se a gente não quiser seguir o DRY, e deixar nosso código molhadinho, ele poderia ficar assim:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;describe&lt;/span&gt; &lt;span class="no"&gt;Foo&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;describe&lt;/span&gt; &lt;span class="s2"&gt;"#bar"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="s2"&gt;"when x is greater than y"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"returns something nice"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="n"&gt;foo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Foo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bar&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="s2"&gt;"when y is greater than x"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"returns something great"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="n"&gt;foo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Foo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bar&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="s2"&gt;"when calc result returns false"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"..."&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="n"&gt;foo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Foo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;allow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Calculator&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;receive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:calc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;and_return&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bar&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="s2"&gt;"when calculator raises error"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"..."&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="n"&gt;foo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Foo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;allow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Calculator&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;receive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:calc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;and_raise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Some error"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bar&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O que eu fiz: Removi os &lt;code&gt;before&lt;/code&gt;, os &lt;code&gt;let&lt;/code&gt; e o &lt;code&gt;subject&lt;/code&gt;, e deixei tudo que um teste precisa dentro dele próprio, ou seja, ele está contido e não tem nada externo que está influenciando na sua execução. Isso facilita a criação de novos testes e a adaptação de testes existentes, você olha para um exemplo e entende o que está acontecendo, sem surpresas.&lt;/p&gt;

&lt;p&gt;Esse código até ficou com menos linhas do que o exemplo anterior, mas não é sobre isso que se trata essa prática, o normal ao aplicar essa prática é ter testes com mais linhas, e isso não pode ser encarado com algo negativo, eu penso que é melhor ter algo mais fácil de ler, entender e modificar mesmo que seja mais extenso do que ter um arquivo compacto que torna a vida do time de desenvolvimento mais difícil.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enxugando um pouquinho
&lt;/h2&gt;

&lt;p&gt;Claro, nem toda prática deve ser seguida cegamente, em 100% de todas as situações. Haverá situações onde reaproveitar código será a melhor saída do que replicar em todos os testes. A diferença é que você poderá fazer isso de forma discreta, apenas quando necessitar, não será sua atitude padrão para cada um dos testes. Por exemplo, em um teste de sistema você precisa reaproveitar uma série de passos que está se repetindo muito no arquivo, você pode criar um método Ruby comum ao invés de utilizar o &lt;code&gt;before&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;describe&lt;/span&gt; &lt;span class="s2"&gt;"Create product"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="s2"&gt;"with valid form"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"creates product"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;store&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:store&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

      &lt;span class="n"&gt;visit_new_product_page&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

      &lt;span class="n"&gt;fill_in&lt;/span&gt; &lt;span class="s2"&gt;"Title"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"My product"&lt;/span&gt;
      &lt;span class="o"&gt;...&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="s2"&gt;"with invalid form"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"displays errors"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;store&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:store&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

      &lt;span class="n"&gt;visit_new_product_page&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

      &lt;span class="n"&gt;fill_in&lt;/span&gt; &lt;span class="s2"&gt;"Title"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
      &lt;span class="o"&gt;...&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;visit_new_product_page&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_role&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:admin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;sign_in&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;

    &lt;span class="n"&gt;visit&lt;/span&gt; &lt;span class="n"&gt;store_products_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;click_on&lt;/span&gt; &lt;span class="s2"&gt;"New product"&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Você mais ranzinza pode estar pensando "é basicamente a mesma coisa de usar um &lt;code&gt;before&lt;/code&gt;", mas é diferente, aqui você definiu um método auxiliar, ele só é executado se você precisar, no momento que você precisar, com os dados que você quer que ele receba, podendo retornar ou não os dados que você precisa, e se precisar de algo diferente você consegue criar um novo método com um novo comportamento.&lt;/p&gt;

&lt;p&gt;Uma outra situação que acho tranquilo enxugar código: Você precisa de um mesmo objeto em testes simples e contidos:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;describe&lt;/span&gt; &lt;span class="no"&gt;Product&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;describe&lt;/span&gt; &lt;span class="s2"&gt;"validations"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:product&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:product&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;is_expected&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;validate_presence_of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:title&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; 
    &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;is_expected&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;validate_presence_of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:price&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; 
    &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;is_expected&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;validate_uniqueness_of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:title&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; 
    &lt;span class="o"&gt;...&lt;/span&gt; 
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ou seja, você vai sentir quando aplicar o DRY será vantajoso e quando repetir código estiver prejudicando a leitura daqueles testes. Então não é como se fosse proibido utilizar certas coisas, é como se agora você precisasse realmente de um motivo forte, e não só fazer porque foi assim que você aprendeu.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusão
&lt;/h2&gt;

&lt;p&gt;O código de teste deve ser considerado código da aplicação, mas é um código com um propósito diferente, e nem sempre as práticas utilizadas no código da regra de negócio podem ser vantajosas ao serem aplicadas nos testes. &lt;/p&gt;

&lt;p&gt;Se você estiver fazendo uma nova aplicação, ou tem liberdade pra aplicar essa técnica numa aplicação existente, faça uma experiência e passe um tempo escrevendo testes WET. No início vai parecer que tem algo errado por conta da duplicação de código, mas depois você vai sentir a produtividade ao ter que adicionar e modificar testes existentes, sem precisar brigar com o RSpec e suas variáveis bagunçadas.&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>rspec</category>
      <category>testing</category>
    </item>
    <item>
      <title>Seu trabalho é sobre código sim</title>
      <dc:creator>Stephann</dc:creator>
      <pubDate>Mon, 25 Sep 2023 14:41:52 +0000</pubDate>
      <link>https://dev.to/stephann/seu-trabalho-e-sobre-codigo-sim-46jg</link>
      <guid>https://dev.to/stephann/seu-trabalho-e-sobre-codigo-sim-46jg</guid>
      <description>&lt;h2&gt;
  
  
  Introdução
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;O trabalho do desenvolvedor não é sobre código, é sobre entregar valor/resolver o problema do cliente. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Essa é uma daquelas frases que qualquer pessoa pode soltar num meio de uma reunião e essa pessoa parecerá inteligente, pois é uma frase que dificilmente pode ser contradita, mas não diz muita coisa.&lt;/p&gt;

&lt;p&gt;Algumas vezes as pessoas querem passar a ideia de "seu código não importa, mas sim se a dor do usuário foi solucionada ou não", e outras vezes quem ouve/lê é quem entende a frase dessa forma. Mas como a maioria dessas afirmações pra ganhar engajamento, essa é uma afirmação superficial e não entra no principal (ou seja, não gera valor hehe), que é "o que é entregar valor?".&lt;/p&gt;

&lt;h2&gt;
  
  
  O que é entregar valor?
&lt;/h2&gt;

&lt;p&gt;Até entendo as pessoas ficarem só na superfície dessa frase feita, porque entregar valor é algo que varia bastante em cada contexto. A resposta pode mudar se levar em consideração pra quem você está gerando valor, o  momento da empresa e os seus objetivos, a sua função e etc.&lt;/p&gt;

&lt;p&gt;Por exemplo, em uma startup em estágio embrionário, gerar valor seria colocar um produto rápido no mercado e que solucione os problemas do público alvo. Já numa empresa em crescimento rápido, talvez gerar valor seja tornar o produto escalável, ou diminuir custos.&lt;/p&gt;

&lt;p&gt;Esses são exemplos clichês e fáceis de dizer que geraram valor porque são métricas facilmente mensuráveis, entregar em 3 meses, aguentar 5000 usuários simultâneos, custos de infraestrutura diminuir em 20.000 reais. Mas tem muita coisa que gera valor e que não é fácil medir o valor gerado, que não temos como afirmar se o impacto causado aconteceu por alguma atitude tomada e consequentemente é mais difícil de vender a ideia para quem toma decisões. Pra exemplificar, vou citar 2 atividades: Escrever código de qualidade e melhorar a developer experience do projeto.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gerando valor invisível
&lt;/h2&gt;

&lt;p&gt;Escrever código de qualidade já é algo difícil de afirmar se está sendo feito ou não, constatar se isso gerou valor é mais difícil ainda. Pro cliente final não parece ter uma relação entre a maior qualidade do código e maior satisfação com o produto, mas pode ser que tenha, posso afirmar que um código com mais qualidade significa um produto com menos bugs pois é mais fácil de manter, e um produto com novas funcionalidades mais cedo pois o código é fácil de modificar e evoluir.&lt;/p&gt;

&lt;p&gt;Melhorar a developer experience também não parece afetar diretamente o cliente final, mas ajuda os desenvolvedores a começar a trabalhar no projeto mais cedo, a solução de problemas é mais rápida por conta de melhores ferramentas pra investigação, menos tempo perdido com reuniões e dúvidas, mais segurança na entrega, mais qualidade de vida, e tudo isso pode afetar também o número de bugs e novas funcionalidades presentes no produto.&lt;/p&gt;

&lt;p&gt;Há vários outros hábitos que parecem ser apenas firula ou perda de tempo, mas que ajudam de alguma forma no processo de desenvolvimento e entrega de um valor visível, da mesma forma como decisões equivocadas e ausências de certos hábitos interferem na entrega de valor mas que também não é fácil mostrar que foram a causa dessa improdutividade. &lt;/p&gt;

&lt;h2&gt;
  
  
  Você é responsável pelo código
&lt;/h2&gt;

&lt;p&gt;É por isso que a afirmação no começo do artigo é superficial. Seu trabalho como desenvolvedor, em muito dos casos, não é saber se o que você está fazendo gera valor diretamente ou não pro cliente, pra isso há diferentes times pra realizar entrevistas com clientes, pra gerenciar projeto, pra analisar métricas, pra definir estratégia, e etc. Se você estiver se preocupando com tudo isso, quem está se preocupando com o código? Ninguém, e é assim sua base de código fica deteriorada, e o trabalho do time de desenvolvimento era não deixar isso acontecer. &lt;/p&gt;

&lt;p&gt;Dá pra fazer uma analogia com a engenharia civil, já que uma pessoa dev pode ser considerada uma pedreira do mundo digital =p. O valor que um pedreiro entrega é uma estrutura boa, resistente, durável, alinhada com as definições do projeto. Outros times que irão definir ou já definiram cores, lugar dos móveis, repartição dos cômodos, iluminação, revestimentos, gerenciamento do projeto. Então como desenvolvedor você deve estar comprometido com o código, outros times estarão comprometidos com prazo, outros com o cliente e etc.&lt;/p&gt;

&lt;h2&gt;
  
  
  Seja um pouco flexível
&lt;/h2&gt;

&lt;p&gt;Mas claro, estar comprometido com o código não é ignorar todo o contexto, você e o time de desenvolvimento não trabalham sozinhos numa ilha, há todo um esforço coletivo na construção de um produto. Então deve haver uma flexibilidade, às vezes é necessário abrir mão de certos processos que seriam "os mais corretos" em prol de um benefício maior.&lt;/p&gt;

&lt;p&gt;Uma situação clássica onde isso deveria acontecer: Há um bug em produção que está gerando prejuízo para a empresa ou para o cliente, o ideal é que isso seja resolvido o mais rápido possível. O processo "correto" seria corrigir o problema, abrir pull request, solicitar revisão, aguardar CI passar, realizar testes de QA e daí realizar deploy. Mas talvez para um ganho de todos, esse fluxo pode ser ignorado e a correção ir direto para produção. Não é o correto em condições normais, mas condições anormais acontecem, você pode minimizar isso registrando uma atividade pra revisar, refatorar e testar o que foi feito. Tenha sempre processos para voltar aos trilhos.&lt;/p&gt;

&lt;p&gt;Agora se isso se torna algo cotidiano, você estiver sempre tendo que comprometer a qualidade do seu trabalho para apagar incêndios, isso pode ser sintoma de uma falta de organização da empresa, falta de maturidade nos processos, e daí você pode tentar corrigir mas se for algo cultural, é melhor partir pra outra ou dançar conforme a música.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusão
&lt;/h2&gt;

&lt;p&gt;Resumindo, diferente da frase que motivou esse artigo, o seu trabalho como desenvolvedor/a é sobre código sim, você focando na qualidade do seu trabalho, numa empresa organizada, você estará contribuindo para um melhor produto e consequentemente gerando mais valor. Quando as pessoas responsáveis pelo código não ligam para ele ou não tomam as melhores decisões, o produto está condenado a uma estagnação e a uma constante correria pra apagar incêndio.&lt;/p&gt;

</description>
      <category>beginners</category>
    </item>
    <item>
      <title>Entendendo webhooks</title>
      <dc:creator>Stephann</dc:creator>
      <pubDate>Fri, 23 Jun 2023 12:35:45 +0000</pubDate>
      <link>https://dev.to/stephann/entendendo-webhooks-2obo</link>
      <guid>https://dev.to/stephann/entendendo-webhooks-2obo</guid>
      <description>&lt;h2&gt;
  
  
  Introdução
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ZxNLMlwv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z3ayb59zg120nyswq7eb.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ZxNLMlwv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z3ayb59zg120nyswq7eb.jpg" alt="A gente já chegou?" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Se você já assistiu Shrek, deve lembrar da cena onde o Burro fica perguntando de tempos em tempos "A gente já chegou?", e isso é basicamente a mesma estratégia muitas plataformas utilizam para se comunicar com outras plataformas, elas ficam "tem informação nova aí pra mim?" de forma recorrente até encontrar o que procura, isso é chamado de &lt;em&gt;polling&lt;/em&gt;. Isso pode trazer algumas consequências como:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Necessidade implementar e observar o funcionamento de uma rotina para buscar dados de tempos em tempos, definindo um intervalo entre suas execuções.&lt;/li&gt;
&lt;li&gt;Esse intervalo pode não ser o ideal, fazendo consultas em um intervalo menor ou maior que o necessário.&lt;/li&gt;
&lt;li&gt;Essas rotinas podem sobrecarregar a plataforma que pergunta e/ou a plataforma que responde, dependendo do volume de dados que precisam ser conferidos e da frequência das perguntas.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Isso pode ser evitado com Webhooks. &lt;/p&gt;

&lt;h2&gt;
  
  
  O problema
&lt;/h2&gt;

&lt;p&gt;Pra contextualizar, vamos imaginar o seguinte cenário: Você está desenvolvendo um e-commerce, e precisa processar o pagamento de um usuário. Você utilizará uma plataforma provedora de pagamento (Stripe, Pagar.me, Iugu e etc.). A forma de pagamento escolhida foi Pix, então quando você consome o serviço da provedora de pagamento, ela te devolve um QR Code e você exibe esse QR Code pro usuário conseguir pagar. Nesse momento você não tem como saber se o usuário pagou, se ele vai pagar em 10 minutos, em 1 hora, em 5 horas ou se vai deixar o Pix expirar, mas você precisa saber o que vai acontecer com esse Pix para poder atualizar o status do pedido na sua plataforma. &lt;/p&gt;

&lt;p&gt;Uma possível solução para esse problema acima: Criar uma rotina pra consumir a API da provedora de pagamento e verificar se o status do pagamento saiu de "Aguardando Pagamento" para "Pago" ou "Vencido". Daí é necessário decidir o intervalo dessa rotina. Se for de hora em hora, você pode criar um problema, pois o pix é instantâneo e o usuário vai ficar preocupado porque a plataforma não mudou o status do pedido dele mesmo depois de 30, 40 minutos após o pagamento. &lt;/p&gt;

&lt;p&gt;Então você muda pra rotina executar a cada minuto, e percebe isso está sobrecarregando sua plataforma e a provedora de pagamento, pois agora você tem 1000 pagamentos pra conferir. Depois você descobre que uma transação de cartão de crédito pode ser estornada até 6 meses depois da data do pagamento e que sua plataforma precisará passar 6 meses conferindo se o status da transação mudou.&lt;/p&gt;

&lt;p&gt;Assim como na cena do Shrek, o ideal para ambas as partes nessa situação seria seu e-commerce parar de perguntar com tanta frequência, ao mesmo tempo que a provedora de pagamento te garantisse que te avisará quando houver alterações nos dados. Uma forma de fazer esse "acordo" entre plataformas é utilizando Webhooks&lt;/p&gt;

&lt;h2&gt;
  
  
  Os webhooks
&lt;/h2&gt;

&lt;p&gt;Webhooks basicamente invertem o conceito de API, podemos pensar como uma API inversa, ao invés do e-commerce consumir um serviço da provedora de pagamento, a provedora de pagamento é que vai consumir o serviço do e-commerce enviando os novos dados.&lt;/p&gt;

&lt;p&gt;Na prática funciona assim: você implementa uma rota para receber as requisições relacionadas à mudança de status dos pagamentos, ex.: &lt;code&gt;minhaloja.com.br/webhooks&lt;/code&gt;, e informa essa URL nas configurações da provedora de pagamento. Com isso configurado, toda vez que um pagamento mudar de status, a provedora de pagamentos irá enviar para a sua aplicação os dados atualizados dos pagamentos sem você precisar pedir de tempos em tempos. Um exemplo de dados que a provedora poderia enviar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;405&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// id do webhook&lt;/span&gt;
  &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;status_changed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// tipo do evento&lt;/span&gt;
  &lt;span class="nx"&gt;transaction_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;125095&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// id do pagamento&lt;/span&gt;
  &lt;span class="nx"&gt;old_status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pending&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// status antigo&lt;/span&gt;
  &lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;paid&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// status atual&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Mas e a segurança?
&lt;/h2&gt;

&lt;p&gt;Agora você deverá estar se perguntando: "Mas se alguém descobrir essa rota e enviar requisições se passando pela provedora de pagamento?". É uma dúvida bem pertinente, de fato você precisa proteger essa URL para que sua aplicação não processe dados de desconhecidos. Uma prática comum no uso de webhooks pra garantir que a requisição é confiável, é implementar a troca de códigos onde apenas duas plataformas saibam como gerar esses códigos. &lt;/p&gt;

&lt;p&gt;Um exemplo dessa estratégia seria utilizar &lt;a href="https://pt.wikipedia.org/wiki/Algoritmo_de_chave_sim%C3%A9trica"&gt;um algoritmo de chave simétrica&lt;/a&gt;: A provedora de pagamento envia no corpo ou no cabeçalho da requisição o código, por exemplo: &lt;code&gt;1213e67a3b34c2848f8317d29bcb8cbc9e0979b8&lt;/code&gt;, e pra você garantir que foi a provedora de pagamento mesmo que enviou essa requisição, você precisa criptografar o corpo do webhook com algum algoritmo específico utilizando sua chave da API, caso o resultado da criptografia seja igual ao do código enviado na requisição significa que a requisição é confiável e pode ser processada.&lt;/p&gt;

&lt;h2&gt;
  
  
  E se minha aplicação sair do ar?
&lt;/h2&gt;

&lt;p&gt;Outra dúvida que pode ter surgido na sua cabeça: "E se minha aplicação ficar fora do ar no momento que a provedora de pagamento estiver enviando webhooks?". Também há soluções pra isso e a mais comum são as retentativas. Caso a aplicação A falhe em receber os webhooks da aplicação B, a aplicação B tentará enviar o webhook outras vezes. Normalmente o que se considera "enviado/recebido com sucesso" é a plataforma devolver um status HTTP de sucesso (status 200, por exemplo) ao receber o webhook.&lt;/p&gt;

&lt;p&gt;O que varia de plataforma para plataforma são as políticas de retentativas. Tem plataforma que vai tentar 3 vezes no máximo (o que considero pouco e não muito confiável), outras vão tentar 30 vezes (🥰 pra mim é o ideal). E também algumas plataformas mudam o intervalo entre retentativas, por exemplo: As 5 primeiras tentativas têm intervalo entre elas de 1 minuto, as 20 próximas tentativas serão de hora em hora, as 5 últimas serão 1 vez ao dia. Isso ajuda a nenhum webhook ficar perdido num eventual problema de comunicação, porque não adiantaria a plataforma fazer 30 retentativas num mesmo minuto, e calhar da sua aplicação ficar fora do ar justamente nesse minuto específico.&lt;/p&gt;

&lt;p&gt;Algumas plataformas também disponibilizam uma API para você consultar os webhooks e dispará-los novamente. Eu encaro essa opção como uma última saída, caso você desconfie de ter perdido o recebimento de algum webhook.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tratando os webhooks
&lt;/h2&gt;

&lt;p&gt;Uma estratégia que venho testando e parece funcionar bem é: Não processar o webhook no momento do seu recebimento. Ou seja, no exemplo do e-commerce, não vá mudar o status do pedido durante o processamento da requisição do webhook, pois pode haver um erro na sua lógica e sua plataforma devolver um status de erro na requisição (status 500, por exemplo). Isso fará a provedora de pagamento enviar várias vezes o webhook desnecessariamente, talvez até causando alguma inconsistência nos seus dados dependendo de como foi implementado. &lt;/p&gt;

&lt;p&gt;A ideia é armazenar esse webhook numa estrutura (numa tabela, por exemplo) e processá-lo posteriormente de forma assíncrona (como um job no Sidekiq). Dessa forma você retorna de forma rápida um status 200 pra plataforma parceira pois você não precisou processar naquele momento, e você tem os dados necessários pra posteriormente processar esse webhook. Caso dê algum problema no processamento, você consegue reprocessar, investigar, debugar aquele webhook com mais calma.&lt;/p&gt;

&lt;h2&gt;
  
  
  Qualidade de vida
&lt;/h2&gt;

&lt;p&gt;Outro ponto que considero que ajudo bastante a utilização dos webhooks, principalmente durante a fase de desenvolvimento e manutenção, é que a URL que vai receber os webhooks e que você informa para a plataforma, possam ser configuradas de maneira automatizada e de forma individualizada.&lt;/p&gt;

&lt;p&gt;Configuração de maneira automatizada significa que você não precisa ir manualmente nas configurações da plataforma pra alterar a URL de recebimento de webhook, isso pode ser feito via código, configuração do projeto, parâmetro na requisição e etc. Isso ajuda nos casos de ter muitas pessoas trabalhando na integração e não precisar ficar alterando manualmente a URL para uma URL de ambiente dev.&lt;/p&gt;

&lt;p&gt;Já a configuração de forma individualizada permite que você defina uma URL pra diferentes recursos que você estiver lidando. No exemplo da provedora de pagamento, essa funcionalidade me permitiria, no momento que eu criar uma transação, enviar algo como &lt;code&gt;"webhook_url": "someurl.com"&lt;/code&gt;, e os webhooks relacionados àquela transação seriam enviados pra essa URL específica. Isso ajuda tanto no ponto anterior de configuração automatizada, quanto em casos de migração e refatoração, onde pros pagamentos novos você consegue apontar os webhooks pra uma nova rota ou nova plataforma sem interferir nos webhooks dos pagamentos antigos.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusão
&lt;/h2&gt;

&lt;p&gt;Os webhooks são um ótimo recurso para dar mais possibilidades de integração entre plataformas, otimizando o poder computacional e garantindo uma melhor consistência e agilidade na atualização dos dados, e em determinados tipos de plataforma são essênciais.&lt;/p&gt;

&lt;p&gt;Se você estiver desenvolvendo uma plataforma que seja necessário utilizar webhooks ou planejando se integrar com alguma que forneça esse recurso, eu recomendo conferir todos os pontos que mencionei no artigo para garantir uma melhor &lt;em&gt;developer experience&lt;/em&gt; (pro seu time ou pra quem vai se integrar com seu produto). Os pontos que considero principais são:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Política de segurança. Prover uma forma pra garantir que as requisições são de quem deveriam ser.&lt;/li&gt;
&lt;li&gt;Política de retentativas. Idealmente com um número alto de retentativas intervaladas de forma progressiva.&lt;/li&gt;
&lt;li&gt;Disponbilização de API para listagem e disparo de webhooks, principalmente se puder filtrar por status (ex.: listar webhooks pendentes) ou por recurso (ex.: listar os webhooks relacionados a pagamento XYZ).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Com esses pontos garantidos, todo mundo sai ganhando, ALEGRIA!!!&lt;/p&gt;

</description>
      <category>webhooks</category>
      <category>development</category>
      <category>api</category>
      <category>integrations</category>
    </item>
    <item>
      <title>Os benefícios de componentizar as views do Rails</title>
      <dc:creator>Stephann</dc:creator>
      <pubDate>Mon, 27 Feb 2023 11:35:44 +0000</pubDate>
      <link>https://dev.to/stephann/os-beneficios-de-componentizar-as-views-do-rails-5di7</link>
      <guid>https://dev.to/stephann/os-beneficios-de-componentizar-as-views-do-rails-5di7</guid>
      <description>&lt;h2&gt;
  
  
  Introdução
&lt;/h2&gt;

&lt;p&gt;Uma coisa que contribui bastante para um código bagunçado e confuso no Ruby on Rails, principalmente em aplicações que crescem e passam pelas mãos de diversas pessoas, é a forma como o Rails lida com o seu front-end, suas views, helpers e partials, e todo o processo de escrever código para gerar o HTML. E nesse artigo eu proponho uma solução para esse problema utilizando as views como objetos.&lt;/p&gt;

&lt;h2&gt;
  
  
  Qual o problema com o front-end do Rails?
&lt;/h2&gt;

&lt;p&gt;Antes de tudo, quero iniciar deixando claro que eu adoro escrever aplicações full stack com o Ruby on Rails. É muito mais fácil resolver certos problemas utilizando o Rails completo do que criar uma aplicação Rails API e construir um SPA (Single Page Application) com um framework javascript. Coisas como autenticação, autorização, SEO, deploy, e outras coisas que devo ter esquecido de mencionar, são horríveis de implementar em aplicações SPA. Mas por alguns motivos, houve um movimento da comunidade na direção de utilizar o Rails apenas para construir as APIs REST mesmo quando isso não era um requisito do produto, e talvez um dos motivos que auxiliou esse movimento tenha sido a facilidade de fazer bagunça na construção do HTML com o Ruby on Rails.&lt;/p&gt;

&lt;p&gt;Pra exemplificar, vou utilizar esse código simples pra expor alguns problemas, é um código em uma partial &lt;code&gt;app/views/layouts/_menu.html.erb&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;badge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;color: :red&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;text: &lt;/span&gt;&lt;span class="vi"&gt;@notifications&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Se alguém quisesse saber de onde veio esse método &lt;code&gt;bagde&lt;/code&gt;, a pessoa teria que buscar nos &lt;code&gt;app/helpers/**/*.rb&lt;/code&gt;, que inclusive poderia estar definido mais de uma vez em múltiplos helpers sem ninguém notar, ou poderia até estar definido em um controller, com o método &lt;code&gt;helper&lt;/code&gt;. Aqui fica evidente um problema, que os helpers são globais por padrão, então qualquer método definido na pasta helpers estará disponível para ser utilizado em qualquer lugar da view. Pra resolver isso, há a configuração &lt;code&gt;config.action_controller.include_all_helpers = false&lt;/code&gt;, que vai adicionar apenas no controller apenas o helper correspondente ao recurso, mas, apesar de ajudar a resolver o problema mencionado, não resolve a tendência dos helpers ficarem inchados com o passar do tempo para comportar os métodos das views e partials daquele recurso.&lt;/p&gt;

&lt;p&gt;Outro problema com o código: De onde esse &lt;code&gt;@notifications&lt;/code&gt; veio? Você procura no controller da tela em questão, por exemplo &lt;code&gt;ArticlesController&lt;/code&gt;, olha nas ações, nos métodos privados, nos callbacks &lt;code&gt;*_action&lt;/code&gt;, e não encontra, depois procura no &lt;code&gt;ApplicationController&lt;/code&gt;, também não encontra, mas descobre que foi incluído o concern &lt;code&gt;app/controllers/concern/notifications_concern.rb&lt;/code&gt; que tinha o seguinte código:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;NotificationsConcern&lt;/span&gt;
  &lt;span class="n"&gt;included&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;after_action&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="vi"&gt;@notifications&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Notification&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="ss"&gt;user: &lt;/span&gt;&lt;span class="n"&gt;current_user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="ss"&gt;read: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Isso expõe o problema, há muitos vetores de entrada de dados nas views. Um callback que instancia uma variável, foi definido num concern, que foi incluído no &lt;code&gt;ApplicationController&lt;/code&gt;, e que uma partial renderizada na view terá acesso direto a essa variável, sem nenhum contrato explícito. Fica mentalmente confuso de entender o código, debuggar, saber do que uma tela precisa pra ser construída, entender o fluxo de dados. Uma solução pra isso seria: Fazer um acordo com o time para que esse tipo de coisa não seja feita, mas não seria tão fácil garantir que a aplicação nunca tenha um código assim.&lt;/p&gt;

&lt;p&gt;E não vou nem me aprofundar na dificuldade de testar código relacionado ao front-end. Mas há como amenizar todos esses problemas mencionados relacionados às views, partials e helpers. Eu penso que tratar as páginas como objetos é um grande começo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tratando as páginas como objetos
&lt;/h2&gt;

&lt;p&gt;Até um tempo atrás, as partials do Rails eram a única forma de componentizar pedaços da sua tela, os helpers também ajudavam nesse ponto, mas como mencionei na seção anterior, esses recursos têm um potencial de conter código insustentável. Recentemente as bibliotecas de componentização têm ganhado seu espaço na comunidade, com a &lt;a href="https://viewcomponent.org" rel="noopener noreferrer"&gt;ViewComponent&lt;/a&gt; sendo a principal biblioteca utilizada para isso, mas há várias alternativas, uma delas é o &lt;a href="https://www.phlex.fun/" rel="noopener noreferrer"&gt;Phlex&lt;/a&gt;, que já testei e recomendo.&lt;/p&gt;

&lt;p&gt;Mas enquanto a maioria dessas bibliotecas focam em componentes (pedaços da tela) e na reutilização desses componentes como um dos grandes benefícios, não há um incentivo ao uso da mesma estratégia para a criação de páginas. Inclusive ao expor que estou utilizando componentes para a criação de páginas, recebi alguns comentários assim: "Utilizar componentes para páginas seria a última coisa que eu faria, pois páginas não são reutilizáveis e não faria sentido componentizá-las", sim isso foi um comentário real no Twitter, não são vozes da minha cabeça, eu juro. Mas será que comentários assim fazem sentido? Talvez, pode ser que sim, realmente as páginas não precisam ser reutilizáveis, mas aplicar a "componetização" nelas traz todos os demais benefícios dessa estratégia, e pra reforçar isso, podemos ler a seguir, de forma resumida, o que o &lt;a href="https://viewcomponent.org/#why-use-viewcomponents" rel="noopener noreferrer"&gt;site do ViewComponent&lt;/a&gt; lista como vantagens na sua utilização:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Por que utilizar o ViewComponent?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Responsabilidade única:&lt;/strong&gt; As lógicas relacionadas às views geralmente estão espalhadas entre modelos, controllers e helpers, diluindo suas reponsabilidades. Com o ViewComponent essa lógica fica encapsulada em suas próprias classes, facilitando o entendimento.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Testes:&lt;/strong&gt; Os componentes são testados individualmente. Dessa forma os testes de integração servem pra testar a aplicação de ponta a ponta, enquanto as variações do conteúdo renderizado são testadas com testes unitários.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fluxos de dados:&lt;/strong&gt; Os templates do rails (views/partials) têm uma interface implítica, tornando difícil de enxergar as dependências. Com ViewComponent, os componentes tem um construtor que identifica fácilmente o que é necessário para a sua renderização.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Qualidade de código:&lt;/strong&gt; Geralmente os templates (views/partials) falham nos padrões de qualidade básico, pois têm métodos longos e condicionais aninhadas em múltiplos níveis. ViewComponents são objetos Ruby, facilitando e garantindo a escrita de código com qualidade.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Pode parecer uma grande loucura o que vou escrever agora, mas eu quero essas vantagens pra minhas páginas também. A seguir explico como estou fazendo isso com o ViewComponent nas aplicações que escrevo:&lt;/p&gt;

&lt;h2&gt;
  
  
  Utilizando o ViewComponent para criar páginas
&lt;/h2&gt;

&lt;p&gt;Como falei anteriormente, as bibliotecas de componentização não focam muito na criação de páginas, mas nada impede que você utilize um componente como uma página. Quando utilizo a ViewComponent eu prefiro criar uma pasta dedicada para as páginas, e deixar a pasta padrão &lt;code&gt;app/components&lt;/code&gt; que a gem sugere para os componentes em si. Minha abordagem é a seguinte:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Crio a pasta &lt;code&gt;app/pages&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Modularizo as classes pelo seu recurso, ou seja, se é uma página de listagem de contatos, ela ficará dentro do módulo &lt;code&gt;Contacts&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Adiciono o sufixo &lt;code&gt;Page&lt;/code&gt; às classes pra ficar claro o que aquela classe representa&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Então uma listagem de contatos ficaria mais ou menos assim:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/pages/contacts/index_page.rb&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Contacts&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;IndexPage&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ViewComponent&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
    &lt;span class="nb"&gt;attr_reader&lt;/span&gt; &lt;span class="ss"&gt;:contacts&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;contacts&lt;/span&gt;&lt;span class="p"&gt;:)&lt;/span&gt;
      &lt;span class="vi"&gt;@contacts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;contacts&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="kp"&gt;private&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;emergency_badge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;contact&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;contact&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;emergency?&lt;/span&gt;
        &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="no"&gt;BagdeComponent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;color: :red&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
          &lt;span class="s2"&gt;"Emergency"&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;%# app/pages/contacts/index_page.html.erb %&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Contacts&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="n"&gt;contacts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;contact&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  ...
  &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;emergency_badge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;contact&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sx"&gt;% end &lt;/span&gt;&lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/controllers/contacts_controllers.rb&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ContactsController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;index&lt;/span&gt;
    &lt;span class="n"&gt;contacts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Contacts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;

    &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="no"&gt;Contacts&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;IndexPage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;contacts: &lt;/span&gt;&lt;span class="n"&gt;contacts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# spec/pages/contacts/index_page_spec.rb&lt;/span&gt;

&lt;span class="no"&gt;RSpec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt; &lt;span class="no"&gt;Contacts&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;IndexPage&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="o"&gt;...&lt;/span&gt;

  &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="s2"&gt;"when some contact is an emergency contact"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"renders emergency bagde"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;contact&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Contact&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s2"&gt;"John"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
        &lt;span class="ss"&gt;email: &lt;/span&gt;&lt;span class="s2"&gt;"x@y.z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
        &lt;span class="ss"&gt;emergency: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;

      &lt;span class="n"&gt;render_inline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;described_class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;contacts: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;contact&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;

      &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;have_css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s2"&gt;"span.bg-red-200"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;text: &lt;/span&gt;&lt;span class="s2"&gt;"Emergency"&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Com esse código podemos ver os problemas mencionados nas seções anteriores sendo resolvidos: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fluxo de dados:&lt;/strong&gt; No controller, fica claro o que é necessário para renderizar a página, nenhuma variável de instância, ex.: &lt;code&gt;@something&lt;/code&gt;, irá "vazar" e ser acessada pelo objeto da página.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Qualidade de código&lt;/strong&gt;: A lógica de renderização é extraída para a classe da página, tornando o template mais simples de ler e manter em comparação com os &lt;code&gt;if&lt;/code&gt;, &lt;code&gt;elses&lt;/code&gt; e um monte de código ruby dentro dos &lt;code&gt;.html.erb&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Responsabilidade única e a bagunça dos helpers é evitada&lt;/strong&gt;: A lógica de renderizar a badge de contato de emergência está bem próxima de onde está sendo utilizada, ou seja, no próprio objeto da página, e não perdida em algum helper, método no controller, modelo e etc.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Testes&lt;/strong&gt;: Conseguimos testar as variações da página com testes unitários, deixando os testes de integração mais simples e focados no que devem fazer.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Claro, você ainda precisará fazer algumas coisinhas pra melhorar a &lt;em&gt;developer experience&lt;/em&gt;, como criar um &lt;code&gt;ApplicationPage&lt;/code&gt; pra compartilhar comportamentos gerais, criar um gerador &lt;code&gt;rails g page contacts/show&lt;/code&gt; pra facilitar a criação de páginas, ajustar a questão das traduções, já que o &lt;code&gt;t(".my_key")&lt;/code&gt; não vai funcionar tão bem por padrão, quando quiser um comportamento compartilhado entre algumas poucas telas/componentes vai ter que extrair pra um módulo e incluir nas respectivas classes, mas nada de outro mundo que não valha a pena a adoção dessa estratégia.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusão
&lt;/h2&gt;

&lt;p&gt;Tenho utilizado essa estratégia nas últimas aplicações que trabalhei e tem funcionado bem, também ando testando a gem &lt;a href="https://www.phlex.fun/" rel="noopener noreferrer"&gt;Phlex&lt;/a&gt; com outras formas de organização e também tem funcionado, e no momento não tenho sentido falta de nada do jeito padrão do Rails trabalhar com seus templates, pelo contrário, tem ajudado a manter uma organização e padronização que eu não tinha anteriormente.&lt;/p&gt;

&lt;p&gt;Inclusive acredito que isso deveria ser uma evolução no Rails, já vir com uma solução dessa disponível embutida no próprio framework, com documentação, com scaffold sendo gerado assim, pois pode até parecer mais verboso no princípio, mas é uma troca que acho que vale muito a pena. As mágicas do Rails são incríveis quando você está vindo de um outro framework e te encantam, mas conforme você enxerga os problemas que algumas mágicas trazem, você passa a querer as coisas da forma mais explícita o possível, mas a gente só aprende isso depois de sofrer um pouco.&lt;/p&gt;

&lt;p&gt;Mas o que eu trouxe aqui não é uma ideia nova, inclusive inspirado no Hanami e no Lucky foi que resolvi fazer algo parecido no Rails. O Hanami tem suas &lt;a href="https://guides.hanamirb.org/v1.3/views/basic-usage/" rel="noopener noreferrer"&gt;views como objetos&lt;/a&gt; e o Lucky (esse é um framework web utilizando a linguagem Crystal) também &lt;a href="https://luckyframework.org/guides/frontend/rendering-html#creating-a-page" rel="noopener noreferrer"&gt;adota a mesma estratégia&lt;/a&gt;, e alguns frameworks javascript, como é o caso do &lt;a href="https://nuxt.com/docs/guide/directory-structure/pages" rel="noopener noreferrer"&gt;Nuxt&lt;/a&gt; têm coisas parecidas. Quem sabe numa versão 8.0 o Rails também entra nesse time.&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>sideprojects</category>
      <category>resources</category>
    </item>
    <item>
      <title>Falha de cobertura: Divagações sobre testes de software</title>
      <dc:creator>Stephann</dc:creator>
      <pubDate>Sun, 11 Dec 2022 23:00:25 +0000</pubDate>
      <link>https://dev.to/stephann/falha-de-cobertura-divagacoes-sobre-testes-de-software-1p1h</link>
      <guid>https://dev.to/stephann/falha-de-cobertura-divagacoes-sobre-testes-de-software-1p1h</guid>
      <description>&lt;h2&gt;
  
  
  Introdução
&lt;/h2&gt;

&lt;p&gt;Uma métrica que recebe bastante atenção no mundo do desenvolvimento é a porcentagem de cobertura dos testes, e quem está entrando na área de desenvolvimento e testes costuma não entender bem o que ela representa. "Nossa base de código está com 100% de cobertura de testes", "Não aceitamos pull requests que diminuam a taxa de código coberto" são frases ditas com orgulho quando alguém vai falar da cultura do seu time de desenvolvimento. E devem ser valorizadas mesmo, pois ter o código 100% coberto com testes é um grande feito na missão de ter uma base de código saudável, que faz o que foi especificado para ser feito e que dá confiança para ser modificado pelas pessoas que mexem nele. O que pretendo trazer nesse texto são algumas situações onde mostro que olhar apenas a porcentagem de cobertura de software não é uma boa ideia, ao mesmo tempo que tento jogar uma luz em como o código deve ser visualizado, entendido, pros comportamentos poderem ser entendidos plenamente, auxiliando na implementação dos testes. Na minha experiência ao ajudar pessoas júniors, uma das maiores dificuldades que elas têm é conseguir saber o que deve ser testado e como deve ser testado, e é o que vou tentar simplificar a seguir.&lt;/p&gt;

&lt;h2&gt;
  
  
  A armadilha dos 100%
&lt;/h2&gt;

&lt;p&gt;A armadilha de ter 100% de cobertura é utilizar essa métrica como referência pra se confiar que o código está bem testado. Um código pode estar com os 100% de cobertura e não estar testado apropriadamente. Explico logo a seguir, mas antes quero deixar o aviso que tudo que vou falar aqui é na minha experiência de testes com Ruby (RSpec/Minitest/Sus) e o Simplecov (ferramenta para analisar a cobertura dos testes), e vou utilizá-los para exemplificar o que quero demonstrar, mas acredito que os mesmos conceitos se encaixam facilmente pra maioria das outras linguagens e ferramentas de testes, então vale a pena continuar lendo.&lt;/p&gt;

&lt;p&gt;Vamos supor que uma aplicação tem o seguinte código:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Products::Create&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save!&lt;/span&gt;

    &lt;span class="no"&gt;Products&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;NotifyUsersJob&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perform_later&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A classe acima é uma operação de criação de produto com um método &lt;code&gt;#call&lt;/code&gt;, que recebe os atributos do produto, salva esse produto e depois envia notificação para os usuários sobre o lançamento daquele produto. &lt;/p&gt;

&lt;p&gt;Um possível teste para essa operação seria:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;RSpec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt; &lt;span class="no"&gt;Products&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Create&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"creates new product"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;operation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Products&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;

    &lt;span class="n"&gt;expect&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="ss"&gt;title: &lt;/span&gt;&lt;span class="s2"&gt;"My product"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;price: &lt;/span&gt;&lt;span class="mf"&gt;50.00&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;change&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:count&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A estratégia do teste está sendo: Instanciar a operação, depois executá-la passando parâmetros válidos e verificar se a contagem de produtos aumentou em 1. O teste deverá passar com tranquilidade, mas pra nossa surpresa, a cobertura de teste ficará 100%.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjgrkiyhgsjpwkvti4auc.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjgrkiyhgsjpwkvti4auc.gif" alt="Meme do pikachu surpreso" width="498" height="325"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Isso deveria ser uma surpresa pra quem não conhece o funcionamento de ferramentas como o Simplecov, pois o código utilizado como exemplo tem 2 funcionalidades: a criação de produto e envio de notificação aos usuários, mas o arquivo de testes está testando apenas o trecho da criação de produto, então na teoria não era pro código estar com 100% de cobertura, mas na prática foi isso que aconteceu. Pra entender melhor esse comportamento precisamos entender o funcionamento do Simplecov.&lt;/p&gt;

&lt;h2&gt;
  
  
  Como o Simplecov funciona?
&lt;/h2&gt;

&lt;p&gt;O Simplecov (&lt;a href="https://github.com/simplecov-ruby/simplecov" rel="noopener noreferrer"&gt;https://github.com/simplecov-ruby/simplecov&lt;/a&gt;) é uma gem que analisa a cobertura de código dos testes. Ao final da execução da bateria de testes da aplicação, o Simplecov fornece um relatório informando a porcentagem de linhas cobertas e uma visualização das linhas cobertas e não cobertas dos arquivos. Algo assim:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr66h4ee610jp9jyeiqsz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr66h4ee610jp9jyeiqsz.png" alt="Exemplo de relatório do Simplecov" width="800" height="620"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Na imagem acima, as linhas verdes são as linhas cobertas com testes, e a linha vermelha indica que não está coberta. Mas entender o que o Simplecov (e essas ferramentas de análise de cobertura) considera "coberto" e "não coberto" é a chave pra entender porque ele considerou aquele código anterior 100% coberto.&lt;/p&gt;

&lt;p&gt;Pro Simplecov, uma linha de código está coberta quando um teste "passa" por ali, e não está coberta quando um teste "não passa" por ali. O "passar" entenda como executar o código em questão. Pra entender melhor vamos olhar para o código a seguir, é uma função de divisão que recebe 2 argumentos, &lt;code&gt;a&lt;/code&gt; é o dividendo e &lt;code&gt;b&lt;/code&gt; é o divisor, e que retorna o quociente. Mas caso o divisor seja 0, é lançado um erro. Também já tem logo na sequência o arquivo de teste, que está testando apenas a situação quando o divisor é diferente de zero:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Calculator&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;divide&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
      &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="s2"&gt;"Division by zero error"&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# Arquivo de teste&lt;/span&gt;
&lt;span class="no"&gt;RSpec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt; &lt;span class="no"&gt;Calculator&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;describe&lt;/span&gt; &lt;span class="s2"&gt;"#divide"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"returns the division of two numbers"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;calculator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Calculator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
      &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;calculator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;divide&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

      &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Testando apenas o caminho feliz, que é quando o divisor não é zero, ao executar os testes o Simplecov mostrou, corretamente, que a linha com &lt;code&gt;raise "Division by zero error"&lt;/code&gt; não está coberta de testes. Isso está correto porque nos nossos testes, não criamos nenhum cenário onde o divisor era zero, e por isso o lançamento de erro nunca foi executado, ou seja, os testes "não passaram" por ali.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr5hfzbu3vv1b14npddn0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr5hfzbu3vv1b14npddn0.png" alt="Simplecov mostrando que o lançamento de erro não está coberto" width="786" height="404"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Então pra ter uma cobertura total do método de divisão, seria necessário criar esse cenário, esse &lt;strong&gt;contexto&lt;/strong&gt;, onde o divisor seja zero e assim os testes consigam "passar" pelo lançamento de erro:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;RSpec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt; &lt;span class="no"&gt;Calculator&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;describe&lt;/span&gt; &lt;span class="s2"&gt;"#divide"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="s2"&gt;"when b is different from zero"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"returns the division of two numbers"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="n"&gt;calculator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Calculator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;calculator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;divide&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="c1"&gt;# Novo contexto &lt;/span&gt;
    &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="s2"&gt;"when b is equal to zero"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"raises 'Division by zero error'"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="n"&gt;calculator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Calculator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
        &lt;span class="n"&gt;expect&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
          &lt;span class="n"&gt;calculator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;divide&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# &amp;lt;= dividindo por 0&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;raise_error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Division by zero error"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzcw6ao7omxscu73ypjtw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzcw6ao7omxscu73ypjtw.png" alt="Simplecov mostrando o método 100% coberto" width="764" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Então agora já entendemos que o Simplecov sabe se a linha de código está coberta verificando se aquela linha foi executada durante a bateria de testes. Isso explica porque ele considerou aquela operação de criação de produtos coberta 100%, pois ao fazer &lt;code&gt;operation.call({ title: "My product", price: 50.0 })&lt;/code&gt; dentro do teste, a linha &lt;code&gt;Products::NotifyUsersJob.perform_later(product)&lt;/code&gt; foi executada, e consequentemente ficou marcada como coberta. E isso pode ser um grande problema.&lt;/p&gt;

&lt;h2&gt;
  
  
  O problema em se apoiar na % de cobertura
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;As ferramentas de cobertura se baseiam na execução das linhas de código e não se o comportamento foi testado&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Como explicado, as ferramentas de cobertura se baseiam na execução das linhas de código e não se o comportamento foi testado. Então é possível ter 100% de cobertura sem ter algo testado apropriadamente. Aproveitando o exemplo já fornecido anteriormente mas alterando a implementação do teste:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Products::Create&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save!&lt;/span&gt;

    &lt;span class="no"&gt;Products&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;NotifyUsersJob&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perform_later&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# Arquivo de teste&lt;/span&gt;
&lt;span class="no"&gt;RSpec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt; &lt;span class="no"&gt;Products&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Create&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"works"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;operation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Products&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
    &lt;span class="n"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="ss"&gt;title: &lt;/span&gt;&lt;span class="s2"&gt;"My product"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;price: &lt;/span&gt;&lt;span class="mf"&gt;50.00&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O teste deixou de verificar que era criado um produto, e agora só está executando o método sem fazer nenhuma verificação pra garantir o comportamento correto do método. Mesmo esse teste dessa forma, o relatório do Simplecov mostrará 100% de cobertura de testes. &lt;/p&gt;

&lt;p&gt;Aí que mora o perigo de se confiar na métrica de cobertura de testes, pois como não é sobre os comportamentos testados, e sim sobre as linhas executadas, alguém pode remover uma linha por engano e ao executar a bateria de testes nenhum teste quebrará, e ainda assim a cobertura se manterá em 100%. No caso da operação de criação de produto, alguém pode refatorar o código, esquece de colocar de volta a linha que envia notificações aos usuários, e como não tem nenhum teste garantindo esse comportamento, estará tudo bem na bateria de testes, mas será um problema se esse código for pra produção, pois os usuários deixarão de receber os avisos de lançamento de produtos e, consequentemente, pode baixar a receita da empresa.&lt;/p&gt;

&lt;p&gt;Pra corrigir essa &lt;strong&gt;falha de cobertura&lt;/strong&gt;, é necessário testar também o envio de notificações aos usuários, e uma forma de testar poderia ser a seguinte:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;RSpec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt; &lt;span class="no"&gt;Products&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Create&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"creates new product"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="c1"&gt;# Novo teste&lt;/span&gt;
  &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"notifies users"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Products&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;NotifyUsersJob&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;receive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:perform_later&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;any_instance_of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="n"&gt;operation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Products&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
    &lt;span class="n"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="ss"&gt;title: &lt;/span&gt;&lt;span class="s2"&gt;"My product"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;price: &lt;/span&gt;&lt;span class="mf"&gt;50.00&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O novo teste está verificando que o job de notificar usuários está sendo executado recebendo uma instância de produto. Talvez ainda não seja o teste ideal mas para a intenção do artigo já serve, pois o comportamento do envio de notificações. E se mesmo antes desse teste a cobertura estava 100%, com a adição do teste o cenário não mudou, continua 100%. Mas achou que finalmente a bateria de testes estava cobrindo todos os comportamentos do código? Achou errado, otário. É necessário enxergar os contextos implícitos.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fop2ookr8iofx2t7sm24s.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fop2ookr8iofx2t7sm24s.gif" alt="Achou errado, otário" width="498" height="278"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Contextos implícitos
&lt;/h2&gt;

&lt;p&gt;O exemplo do método &lt;code&gt;#divide&lt;/code&gt; da calculadora tinha dois contextos bem claros, estava na cara: "Quando o divisor é zero" e "Quando o divisor não é zero". Isso é facilmente visível pela presença do &lt;code&gt;if&lt;/code&gt; e &lt;code&gt;else&lt;/code&gt; (o &lt;code&gt;unless&lt;/code&gt; e &lt;code&gt;elsif&lt;/code&gt; também ajudam a enxergar esses contextos). Pra facilitar a visualização desses contextos, podemos rascunhar um fluxograma, ficaria algo como:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9boldkezy82dvqr0aa3q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9boldkezy82dvqr0aa3q.png" alt="Fluxogram do método de divisão" width="800" height="1082"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Com o fluxograma conseguimos enxergar mais facilmente uma divisão de caminhos no comportamento desse método. Ao tomar uma decisão, que é verificar se o divisor é zero, o código segue por um caminho ou outro. Esses são os contextos. Quando sentir dificuldade de visualizar os contextos, você pode criar esses diagramas numa folha de papel, num programa ou mentalmente, todo código que você escreve é um processo e pode ser traduzido num fluxograma, e cada decisão que o código toma, pode ser entendido como um contexto, e esses contextos devem ser testados, ou pelo menos deveriam ser testados. Um exemplo mais complexo de código e seu fluxograma correspondente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;shipping_label&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;digital?&lt;/span&gt; 
    &lt;span class="s2"&gt;"Delivery via email"&lt;/span&gt;
  &lt;span class="k"&gt;elsif&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;shipping_fee&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="s2"&gt;"Free shipping"&lt;/span&gt;
  &lt;span class="k"&gt;elsif&lt;/span&gt; &lt;span class="n"&gt;current_user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;premium?&lt;/span&gt;
    &lt;span class="s2"&gt;"Free shipping (Premium subscriber)"&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;shipping_fee&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpkmmkleup3wxywh8u6m0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpkmmkleup3wxywh8u6m0.png" alt="Fluxograma do label do frete" width="800" height="1039"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Pelo fluxograma podemos enxergar que o código toma várias decisões e tem um fluxo bem ramificado. Então o teste desse código teria vários contextos, por exemplo: "Quando o produto é digital", "Quando o produto é físico e tem frete grátis", "Quando o produto é físico com taxa de frete e o usuário é assinante premium" e etc. Se os testes não refletirem esses cenários, grandes chances do código não estar testado apropriadamente.&lt;/p&gt;

&lt;p&gt;Mas beleza, se esses contextos não eram fáceis de enxergar antes, com a explicação deve ter ficado um pouco mais claro. Mas tem os contextos que são "invisíveis", são implícitos, não tem um &lt;code&gt;if&lt;/code&gt;/&lt;code&gt;else&lt;/code&gt; ajudando a detectar. Por exemplo, começando por um fácil:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;avatar_url&lt;/span&gt;
    &lt;span class="n"&gt;profile_picture_url&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;placeholder_avatar_picture_url&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O código acima retorna a foto de perfil do usuário caso exista, e caso não exista será retornada uma url de uma placeholder pra não deixar o avatar vazio. Ao explicar o funcionamento do método já foi possível detectar dois contextos: "Quando o usuário tem foto de perfil" e "Quando o usuário não tem foto de perfil". Isso se deve porque o código anterior pode ser traduzido em:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;avatar_url&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;profile_picture_url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;present?&lt;/span&gt;
    &lt;span class="n"&gt;profile_picture_url&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="n"&gt;placeholder_avatar_picture_url&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Os testes poderiam ser organizados assim:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;describe&lt;/span&gt; &lt;span class="s2"&gt;"#avatar_url"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="s2"&gt;"when profile picture url is present"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"returns profile picture url"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="o"&gt;...&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="s2"&gt;"when profile picture url is blank"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"returns placeholder avatar picture url"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="s2"&gt;"..."&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Outro exemplo de um contexto implícito:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;company_name&lt;/span&gt;
    &lt;span class="n"&gt;company&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt; &lt;span class="c1"&gt;# ou company.try(:name)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O &lt;code&gt;&amp;amp;.&lt;/code&gt; no Ruby é o safe navigation operator (no Rails pode ser utilizado o &lt;code&gt;#try&lt;/code&gt;), serve pra executar o método apenas quando o objeto em questão estiver presente, evitando a chamada de um método numa referência nula. Então podemos entender que o &lt;code&gt;#company_name&lt;/code&gt; tem 2 contextos: "Quando a empresa está presente" e "Quando a empresa não está presente". O código anterior poderia ser escrito também assim:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;company_name&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;company&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;present?&lt;/span&gt;    
      &lt;span class="n"&gt;company&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt; 
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="kp"&gt;nil&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;E os testes em ambos os casos seriam:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;describe&lt;/span&gt; &lt;span class="s2"&gt;"#company_name"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="s2"&gt;"when company is present"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"returns company name"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="o"&gt;...&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="s2"&gt;"when company is blank"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"returns nil"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="s2"&gt;"..."&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Agora contextos um pouco mais difíceis de enxergar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Posts::Publish&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;post&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;state: :published&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;published_at: &lt;/span&gt;&lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;zone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Um fluxograma mais descuidado poderia ser feito assim:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhxegaxlotdkynl6fly5o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhxegaxlotdkynl6fly5o.png" alt="1º versão do fluxo de publicação de post" width="581" height="1639"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;O fluxo acima está parcialmente correto porque está registrando apenas o caminho feliz da execução, mas caso a atualização do post falhe o que acontece? O fluxo não está considerando isso, e esse é um dos contextos implícitos. Vamos corrigir o fluxograma:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffxancimj38apj3czetg4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffxancimj38apj3czetg4.png" alt="2º versão do fluxo de publicação de post" width="800" height="1850"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Agora o fluxo considera que a atualização pode falhar e assim tem dois contextos: "Quando a atualização é bem sucedida" e "Quando a atualização falha". Mas ainda tem outro contexto implícito que é: E se o post não existir na base de dados? Vamos atualizar o fluxograma pra incluir esse cenário:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwtnl8prpfauw8uc0zqzl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwtnl8prpfauw8uc0zqzl.png" alt="3º versão do fluxo de publicação de post" width="800" height="1027"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Agora o fluxo está completo, e podemos perceber que mesmo o método tendo apenas 2 linhas, sem nenhuma condicional, é representado por um fluxo com 3 caminhos diferentes. Isso é devido aos contextos implícitos, que diferente dos &lt;code&gt;if&lt;/code&gt;/&lt;code&gt;else&lt;/code&gt;, você precisa entender o funcionamento interno dos métodos utilizados pra saber, por exemplo, se uma exceção será lançada e se o método retorna diferentes coisas dependendo da execução, que é o caso do &lt;code&gt;find&lt;/code&gt; e do &lt;code&gt;update&lt;/code&gt;, mas poderia ser qualquer outro método. &lt;/p&gt;

&lt;p&gt;Com os contextos detectados, poderíamos escrever os testes pra esse método da seguinte forma:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;RSpec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt; &lt;span class="no"&gt;Posts&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Publish&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="s2"&gt;"when post with given id cannot be found"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"raises ActiveRecord::RecordNotFound error"&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="s2"&gt;"when update is successful"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"publishes post"&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;
    &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"returns true"&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="s2"&gt;"when update fails"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"doesn't publish post"&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;
    &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"returns false"&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Se o fluxo não tivesse sido destrinchado e fossémos utilizar a primeira versão pra implementar nossos testes, teria ficado algo assim:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;RSpec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt; &lt;span class="no"&gt;Posts&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Publish&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"publishes post"&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;E no Simplecov, ambos os testes resultariam em 100% de cobertura do método. Mas qual você abordagem você acha que garante melhor o funcionamento do código? Pra mim é o teste com os três contextos. Por isso reforço que a % cobertura de teste por si só não é relevante pra medir a qualidade dos testes, é necessário um melhor entendimento do que está sendo testado por parte da pessoa que está escrevendo código e por parte das pessoas que vão revisar aquele código, assim os testes "meia boca" podem ser detectados e melhorados. &lt;/p&gt;

&lt;p&gt;E ainda há outros tipos de contexto implícito, tipo esse:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt;
  &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transaction&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="no"&gt;Customer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="no"&gt;Store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O código acima cria um cliente e depois atualiza a loja. O contexto implícito além do &lt;code&gt;create!&lt;/code&gt;, que pode gerar uma exceção, é que caso ocorra uma falha na atualização da loja, a criação do cliente deve ser desfeita devido à criação e atualização estarem englobados numa transação do banco de dados. Então se isso for um requisito do código, o ideal é que haja um teste cobrindo esse comportamento, pois se alguém remover essa transaction, deve receber um aviso que está alterando o comportamento do código.&lt;/p&gt;

&lt;p&gt;E vão ter outros contextos não tão implícitos assim mas que vale a pena citar rapidamente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# um if inline continua tendo 2 contextos&lt;/span&gt;
&lt;span class="n"&gt;button_class&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"active"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;featured?&lt;/span&gt;

&lt;span class="c1"&gt;# um if ternário continua tendo 2 contextos&lt;/span&gt;
&lt;span class="n"&gt;submit_text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;persisted?&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="s2"&gt;"Update"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"Create"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Em ambos os casos o Simplecov por padrão tratará a linha como coberta, mesmo se o teste não cobrir os diferentes cenários.&lt;/p&gt;

&lt;p&gt;Então voltando pro nosso primeiro exemplo, da operação de publicação de produto, temos um contexto implícito lá, que é ao utilizar o &lt;code&gt;#save!&lt;/code&gt;, caso os atributos sejam inválidos, uma exceção será lançada e a execução do método abortada. Então atualizando nossos testes pra considerar essa situação, seria algo como:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;RSpec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt; &lt;span class="no"&gt;Products&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Create&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="s2"&gt;"with valid attributes"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"creates new product"&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;
    &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"notifies users"&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="c1"&gt;# novo contexto&lt;/span&gt;
  &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="s2"&gt;"with invalid attributes"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"raises ActiveRecord::RecordInvalid error"&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;
    &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"doesn't notify users"&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="c1"&gt;# &amp;lt;= maior segurança aqui&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;E agora podemos considerar que nossos testes estão suficientes, pois verifica se com os parâmetros válidos, o produto está sendo criado e as notificações disparadas, e com os parâmetros inválidos, verificamos o lançamento da exceção e que nenhum usuário foi notificado.&lt;/p&gt;

&lt;h2&gt;
  
  
  Paranóia
&lt;/h2&gt;

&lt;p&gt;Gosto de dizer pras pessoas que ajudo a se desenvolver na programação que o objetivo dos testes é fazer o código ser à prova de sabotagem. O "sabotador" pode ser uma pessoa do time, uma pessoa nem entrou mas ainda vai entrar no time, ou até mesmo a própria pessoa que no futuro precisará voltar pra dar manutenção naquele código. O teste deve detectar se mesmo após as modificações realizadas no código, o código está fazendo o que foi especificado pra fazer. Utilizando ainda a operação de publicação de produto como exemplo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Products::Create&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save!&lt;/span&gt;

    &lt;span class="no"&gt;Products&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;NotifyUsersJob&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perform_later&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Se a pessoa ao dar manutenção no código, remover sem intenção a linha de disparo de notificações, o teste deve quebrar pois um requisito era enviar notificação aos usuários após a criação do produto. Se trocar o &lt;code&gt;save!&lt;/code&gt; pelo &lt;code&gt;save&lt;/code&gt;, o teste deve quebrar, pois sem isso o código disparará as notificações mesmo quando o produto estiver inválido, fazendo os usuários receberem notificações de produto que não existe. Geralmente esses erros acontecem por desatenção, por não conhecer a base de código, e o papel do teste é defender o funcionamento do código.&lt;/p&gt;

&lt;p&gt;Há lendas de que na Netflix existe o Macaco do Caos, um serviço na infraestrutura que desliga um servidor da empresa propositalmente pra verificar se os sistemas continuam funcionando, se adaptando à situação sem intervenção manual e sem causar nenhum impacto ao usuário final. Depois isso evoluiu pra um exército de macacos que fazem diferentes tipos de sabotagem, como diminuir o tempo de resposta de um serviço, por exemplo. Então podemos assumir que os times de desenvolvimento são compostos por vários macacos do caos bem intencionados, ou seja, o propósito deles não é causar um problema intencionalmente, mas sim adicionar novas funcionalidades, corrigir problemas, mas devido à natureza do desenvolvimento de software, estão suscetíveis a adicionar problema no código, e a função dos testes é ser à prova de macacos do caos, aumentando as chances de códigos defeituosos serem detectados antes de chegarem em produção e causarem efeitos colaterais aos usuários da aplicação.&lt;/p&gt;

&lt;p&gt;Então a mentalidade ao escrever testes deve ser:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Primeiro entender o que o código faz&lt;/li&gt;
&lt;li&gt;Identificar os contextos explícitos e explícitos&lt;/li&gt;
&lt;li&gt;Definir o que é essencial no funcionamento do código&lt;/li&gt;
&lt;li&gt;Garantir, com testes, que o código está fazendo o que é pra fazer&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Dessa forma, quando alguém alterar o código e quebrar algo, será avisado que sua alteração mudou o comportamento da aplicação, se for algo intencional, os testes devem ser alterados pra refletirem o novo comportamento, ou o código deve ser corrigido pra manter o comportamento original.&lt;/p&gt;

&lt;p&gt;Dependendo do nível de paranóia de quem está implementando os testes, proteger o código com testes pode ser bem trabalhoso. Vamos utilizar uma método de soma como exemplo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Calculator&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# Arquivo de teste&lt;/span&gt;
&lt;span class="no"&gt;RSpec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt; &lt;span class="no"&gt;Calculator&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;describe&lt;/span&gt; &lt;span class="s2"&gt;"#sum"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"returns the sum of two numbers"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;calculator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Calculator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
      &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;calculator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

      &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O teste está bem direto, verifica que chamando o &lt;code&gt;#sum&lt;/code&gt; com 5 e 2, retorna 7. Isso já garante que a soma está funcionando como deveria. Mas seguindo a ideia de que tem vários macacos do caos no time, algum deles no futuro poderia alterar o método &lt;code&gt;#sum&lt;/code&gt; pra algo assim:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Calculator&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="mi"&gt;7&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Os testes não quebrariam mesmo com o código claramente errado. Então pra se proteger dos macacos do caos os testes poderiam ser ajustados:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;RSpec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt; &lt;span class="no"&gt;Calculator&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;describe&lt;/span&gt; &lt;span class="s2"&gt;"#sum"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"returns the sum of two numbers"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;calculator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Calculator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;

      &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;calculator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;
      &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;calculator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;59&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt; &lt;span class="mi"&gt;64&lt;/span&gt;
      &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;calculator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

      &lt;span class="c1"&gt;# ou&lt;/span&gt;
      &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;rand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;rand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;calculator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pronto, agora já está evitando que seja colocado um número fixo no retorno do método, e o agora teste avisará ao macaco do caos que aqui ele não se cria. Mas toda vez que cria uma defesa pra esses casos, podem surgir vários outros, exemplo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Calculator&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;
    &lt;span class="k"&gt;elsif&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;59&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;64&lt;/span&gt;
    &lt;span class="k"&gt;elsif&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt; 
      &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delete_all&lt;/span&gt;
      &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O macaco do caos, que parece estar bem mal intencionado agora, conseguiu burlar sua proteção, e ainda colocou algo mais perigoso, colocou um código que destrói toda sua base de usuários, e o teste está passando sem problemas. Claro, tudo isso é ficção, esse tipo de coisa não vai acontecer em times saudáveis, mas serve pra mostrar que não é possível defender seu código de todas as possíveis alterações que podem ser feitas, principalmente garantir que coisas não esperadas não foram feitas, e que é improdutivo se tentar alcançar esse nível de defesa. Mas de toda forma, é possível colocar algumas garantias em contextos relevantes que são mais propícios de acontecer, por exemplo, garantir que um e-mail não foi enviado caso o produto esteja inválido, garantir que o item de pagamento mudou o status para reembolsado ao chamar com sucesso o endpoint de reembolso da API de gateway, e etc.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusão
&lt;/h2&gt;

&lt;p&gt;No fim, testar código é uma arte, uma habilidade que pode e precisa ser desenvolvida, é um jogo onde você sempre está negociando clareza e legibilidade contra segurança e rigidez. Testes são códigos da aplicação e também recebem manutenção, também são lidos, e é necessário ver até onde faz sentido degradar a legibilidade e manutenibilidade pra garantir um teste, às vezes desnecessariamente, mais seguro. Há pontos cruciais da aplicação que você precisa cobrir todos os contextos e todos os retornos pois se algo ali mudar, pode dar problema sério, geralmente são os pontos que geram dinheiro pra empresa, como o login, checkout de pagamento, ou que podem fazer a empresa perder muito dinheiro, como uma falha de segurança ou vazamento de dados. E tem outros códigos que são menos críticos e que você pode só testar que funciona sem quebrar pois se eles um dia deixarem de funcionar, o estrago será pequeno, sendo corrigido facilmente, não há nada de errado em ter testes frágeis nesses casos. Também há outros mecanismos que auxiliam a evitar problemas maiores, mais cedo, antes de um eventual problema se tornar um problema crítico, como na revisão de código dos pull requests, QA manual/automático, ambiente de staging/homologação, feature flags na liberação de novas funcionalidades antes de liberar para o público geral e etc, então o desafio é sempre encontrar um modelo de desenvolvimento sustentável da base de código.&lt;/p&gt;

</description>
      <category>watercooler</category>
    </item>
    <item>
      <title>Evitando IDs sequenciais no Ruby on Rails</title>
      <dc:creator>Stephann</dc:creator>
      <pubDate>Wed, 30 Mar 2022 13:12:39 +0000</pubDate>
      <link>https://dev.to/stephann/evitando-ids-sequenciais-no-ruby-on-rails-5l9</link>
      <guid>https://dev.to/stephann/evitando-ids-sequenciais-no-ruby-on-rails-5l9</guid>
      <description>&lt;h2&gt;
  
  
  Introdução
&lt;/h2&gt;

&lt;p&gt;Por padrão as aplicações Ruby on Rails utilizam identificadores sequenciais para representar a chave primária de uma tabela. Por alguns motivos esse comportamento pode não ser o desejado, seja por revelar informações estratégicas do produto ou por facilitar que alguém mal intencionado explore falhas da aplicação. Por exemplo, se um sistema utiliza ID sequenciais, fica fácil descobrir quantos usuários a empresa tem ou então quantos pedidos ela está recebendo. Ou então, alguém criar um script que saia percorrendo os IDs de 1 em 1 extraindo as informações disponíveis, sejam públicas ou que foram expostas por alguma falha de autorização. Uma alternativa que pode ser utilizada para evitar esses problemas é o UUID. Nesse artigo mostro como configurar sua aplicação para utilizar o UUID como a chave primária de suas tabelas.&lt;/p&gt;

&lt;p&gt;Como toda escolha que fazemos no desenvolvimento de software traz vantagens e desvantagens, essa decisão entre UUID x Inteiro não seria diferente, vale a pena ler o artigo do Rafael Ponte sobre esse assunto: &lt;a href="https://rponte.com.br/2021/01/30/nao-use-uuid-como-pk-nas-tabelas-do-seu-banco-de-dados/"&gt;https://rponte.com.br/2021/01/30/nao-use-uuid-como-pk-nas-tabelas-do-seu-banco-de-dados/&lt;/a&gt;. Leve em consideração o peso de utilizar UUIDs como chave primárias e analise se faz sentido para o seu caso.&lt;/p&gt;

&lt;h2&gt;
  
  
  O que é UUID?
&lt;/h2&gt;

&lt;p&gt;O UUID é um unificador único universal, que também pode ser chamado de GUID (identificador único global). Ele é formado por 32 dígitos hexadecimais agrupados em 5 grupos separados por hífens. Exemplos de UUID:&lt;/p&gt;

&lt;p&gt;71b69829-813b-44c7-b75d-8a2f3728520d&lt;br&gt;
ec1a3b99-528f-4f90-a8dc-46f467848b93&lt;br&gt;
a04f2e7c-47ab-419d-bfab-32fd4492d80e&lt;br&gt;
f9daaec4-b915-4e07-8690-6246b48a1cb9&lt;/p&gt;

&lt;p&gt;Imaginando a URL de detalhes de um pedidos de compra, ao invés de &lt;code&gt;meusite.com/orders/32&lt;/code&gt;, ela seria algo assim: &lt;code&gt;meusite.com/orders/71b69829-813b-44c7-b75d-8a2f3728520d&lt;/code&gt;. Ou seja, não revela nada sobre a quantidade de pedidos que a aplicação recebeu, e caso alguém queira tentar acessar pedidos de outros usuários, será quase impossível de advinhar os IDs de pedidos existentes.&lt;/p&gt;
&lt;h2&gt;
  
  
  Instalando a extensão do PostgreSQL
&lt;/h2&gt;

&lt;p&gt;A função que o PostgreSQL utilizará para gerar os UUID para as nossas chaves primárias é a &lt;code&gt;gen_random_uuid()&lt;/code&gt;, uma função da extensão &lt;code&gt;pgcryto&lt;/code&gt; que não vem instalada por padrão. Então para instalá-la, primeiro crie uma migração:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;bundle&lt;/span&gt; &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="n"&gt;rails&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt; &lt;span class="n"&gt;migration&lt;/span&gt; &lt;span class="n"&gt;enable_pgcrypto_extension&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;E no arquivo gerado coloque:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;EnablePgcryptoExtension&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Migration&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;6.1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;change&lt;/span&gt;
    &lt;span class="n"&gt;enable_extension&lt;/span&gt; &lt;span class="s1"&gt;'pgcrypto'&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ao executar um &lt;code&gt;bundle exec rails db:migrate&lt;/code&gt;, a extensão estará instalada e pronto para ser utilizada.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configurando os generators para utilizar o UUID
&lt;/h2&gt;

&lt;p&gt;Mesmo com a extensão instalada, se você gerar um modelo ou um scaffold, as estruturas de banco de dados continuarão sendo geradas com um ID sequencial. Isso porque não foi configurado nada no Rails ainda para que ele utilizasse o UUID. Então vamos fazer isso. Crie o arquivo &lt;code&gt;config/initializers/generators.rb&lt;/code&gt; e adicione o seguinte código:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generators&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;orm&lt;/span&gt; &lt;span class="ss"&gt;:active_record&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;primary_key_type: :uuid&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Agora ao gerar um modelo a migração será criada assim:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CreateOrders&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Migration&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;6.1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;change&lt;/span&gt;
    &lt;span class="n"&gt;create_table&lt;/span&gt; &lt;span class="ss"&gt;:orders&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;id: :uuid&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt; &lt;span class="ss"&gt;:description&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A diferença é o parâmetro &lt;code&gt;id: :uuid&lt;/code&gt; passado no &lt;code&gt;create_table&lt;/code&gt;, que fará o tipo da coluna ID ser UUID. Outra coisa que essa configuração faz, é informar automaticamente o tipo do ID na definição da chave estrangeira. Explicando melhor: Se por acaso eu precise adicionar a coluna com o ID do pedido na tabela pagamento, essa coluna precisa ser do tipo UUID também. Então caso eu execute um &lt;code&gt;bundle exec rails g migration add_order_to_payments order:reference&lt;/code&gt;, será gerada uma migração assim:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AddOrderToPayments&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Migration&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;6.1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;change&lt;/span&gt;
    &lt;span class="n"&gt;add_reference&lt;/span&gt; &lt;span class="ss"&gt;:payments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:order&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
      &lt;span class="ss"&gt;null: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
      &lt;span class="ss"&gt;foreign_key: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
      &lt;span class="ss"&gt;type: :uuid&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note o &lt;code&gt;type: :uuid&lt;/code&gt; nos parâmetros do &lt;code&gt;add_reference&lt;/code&gt;, isso fará a coluna &lt;code&gt;order_id&lt;/code&gt; ser do tipo UUID e assim poderá se referenciar a outra tupla da tabela orders. &lt;/p&gt;

&lt;h2&gt;
  
  
  Finalizando a configuranção
&lt;/h2&gt;

&lt;p&gt;Basicamente já está tudo configurado e pronto para a utilização, não precisa fazer mais nada. Podemos confirmar isso criando objetos pelo terminal com o &lt;code&gt;bundle exec rails c&lt;/code&gt; e executando algum &lt;code&gt;Order.create(description: 'Some description')&lt;/code&gt; ou então utilizando as telas normalmente para cadastrar os dados. Os IDs estarão preenchidos normalmente com os UUID, a navegação na URL também estará funcionando normalmente.&lt;/p&gt;

&lt;p&gt;Se você for curioso o suficiente, ao navegar nas telas ou executando algo como Order.first ou Order.last, perceberá que a ordenação está estranha. O &lt;code&gt;Model.first&lt;/code&gt; nem sempre retornará o primeiro registro criado, e o &lt;code&gt;Model.last&lt;/code&gt; poderá não retornar o registro mais recente. Isso se deve ao fato do Rails, por padrão, ordenar as consultas pelo ID, ou seja, ele automagicamente faz por baixo algo como um &lt;code&gt;ORDER BY id&lt;/code&gt; nas consultas. Como o UUID não é gerado em uma ordem específica, o primeiro registro criado pode ter seu ID iniciando com &lt;code&gt;ec1a3...&lt;/code&gt;, e o último criado ter o ID iniciando com &lt;code&gt;a04f2...&lt;/code&gt;,  então ao fazer um Model.first, o Rails trará na verdade o último registro criado.&lt;/p&gt;

&lt;p&gt;Mas isso pode ser configurado, é bem simples. Essa configuração é feita no arquivo &lt;code&gt;app/models/application_record.rb&lt;/code&gt;, pois é a classe que todos os outros modelos herdam. Esse arquivo deve ficar assim:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ApplicationRecord&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
  &lt;span class="n"&gt;primary_abstract_class&lt;/span&gt;
  &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;implicit_order_column&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="ss"&gt;:created_at&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Foco na linha &lt;code&gt;self.implicit_order_column = :created_at&lt;/code&gt;, que fará o Rails utilizar a coluna &lt;code&gt;created_at&lt;/code&gt; para ordenar os registros caso não seja explicitamente informado nenhum outro campo de ordenação.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusão
&lt;/h2&gt;

&lt;p&gt;É isso. É algo simples, fácil, que não interfere em controller, nas rotas, o uso do framework continua basicamente o mesmo e você ainda tem todas as vantagens de não revelar informações estratégicas sobre o produto.&lt;/p&gt;

&lt;p&gt;Um curiosidade para encerrar: O UUID não é único, o &lt;code&gt;gen_random_uuid()&lt;/code&gt; do PostgreSQL pode gerar um UUID repetido em uma mesma tabela. Mas não precisa se preocupar, mesmo que você tenha trilhões de registros em uma tabela, é mais fácil você morrer devido a uma queda de meteorito na sua cabeça, do que ser gerado um UUID duplicado, e quando isso acontecer, não será um problema grande, pois as chaves primárias do PostgreSQL têm restrição de unicidade. Mas se acontecer: Que azar, hein? Cuidado ao sair de casa.&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>uuid</category>
      <category>postgres</category>
    </item>
    <item>
      <title>Utilizando views SQL no Ruby on Rails</title>
      <dc:creator>Stephann</dc:creator>
      <pubDate>Mon, 28 Mar 2022 14:12:02 +0000</pubDate>
      <link>https://dev.to/stephann/utilizando-views-sql-no-ruby-on-rails-342h</link>
      <guid>https://dev.to/stephann/utilizando-views-sql-no-ruby-on-rails-342h</guid>
      <description>&lt;h2&gt;
  
  
  Introdução
&lt;/h2&gt;

&lt;p&gt;Na maior parte do tempo, o desenvolvedor Rails quando precisa de alguma informação do banco de dados, usa a API do Active Record pra fazer suas consultas, mas às vezes isso não é suficiente para trazer os dados desejados e é necessário escrever código SQL puro na aplicação. Geralmente isso significa que a informação é complexa demais pra ser obtida apenas as instruções básicas &lt;code&gt;SELECT&lt;/code&gt;, &lt;code&gt;WHERE&lt;/code&gt;, &lt;code&gt;JOIN&lt;/code&gt;, ou então significa que ela é volátil o bastante pra não virar uma tabela fixa no banco de dados. Por exemplo, os vendedores com o maior volume de negociações no ano, uma listagem com os produtos do mercantil mais vendidos em diferentes momentos do dia ou o ticket médio dos clientes por faixa etária. Para dificultar ainda, às vezes é necessário permitir que esses dados sejam ordenados, filtrados e paginados, o que pode adicionar mais complexidade ainda para a consulta.&lt;/p&gt;

&lt;p&gt;Uma forma de simplificar essas dores é abstrair essas consultas utilizando as views do banco de dados, que são basicamente tabelas virtuais, ou seja, os não estão persistidas no disco mas fornecem algumas facilidades das tabelas tradicionais. Com isso, ao invés de lidar sempre com uma consulta complexa do SQL, pode ser usado apenas o nome da view  para pegar as informações pretendidas. E como a view se comporta como uma tabela, é possível mapear um modelo do ActiveRecord para uma view do banco de dados com apenas algumas modificações simples e é isso que vou mostrar nesse artigo.&lt;/p&gt;

&lt;p&gt;Pra facilitar, e também apresentar uma biblioteca que pode ser desconhecida pra alguns, vou utilizar a gem &lt;a href="https://github.com/scenic-views/scenic" rel="noopener noreferrer"&gt;scenic&lt;/a&gt; para gerenciar as views da aplicação Rails, mas é opcional, quem não puder ou não quiser adicionar essa dependência no projeto, tudo pode ser feito tranquilamente sem ela. Quem for seguir SEM a scenic, ficam as dicas:&lt;/p&gt;

&lt;p&gt;A criação de views é feita nas migrations utilizando o comando &lt;code&gt;execute&lt;/code&gt;, por exemplo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CreateMinhaView&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Migration&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;6.1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;up&lt;/span&gt;
    &lt;span class="n"&gt;execute&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;-&lt;/span&gt;&lt;span class="no"&gt;SQL&lt;/span&gt;&lt;span class="sh"&gt;
      CREATE OR REPLACE VIEW public.minha_view
      ...
&lt;/span&gt;&lt;span class="no"&gt;    SQL&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;down&lt;/span&gt;
    &lt;span class="n"&gt;execute&lt;/span&gt; &lt;span class="s2"&gt;"DROP VIEW public.minha_view"&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;E para armazená-las corretamente no dump do schema, é necessário mudar o seu formato para &lt;code&gt;:sq&lt;/code&gt;l lá no &lt;code&gt;application.rb&lt;/code&gt;, assim:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;MeuApp&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Application&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Application&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;
    &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;active_record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;schema_format&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="ss"&gt;:sql&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Vale ressaltar que utilizarei o PostgreSQL como banco de dados, talvez para outros bancos de dados sejam necessárias algumas modificações no que vou ensinar. E quando eu me referir a views nesse artigo, serão sempre as views do banco de dados, e não as views html do projeto.&lt;/p&gt;

&lt;h2&gt;
  
  
  Contexto
&lt;/h2&gt;

&lt;p&gt;O problema que vamos solucionar é o seguinte: Nossa aplicação é um quadro de estatísticas que serve para registrar as conquistas dos jogadores de uma comunidade de e-sports. A modelagem é simples, é a seguinte:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmxaya48c2588uv9kkux8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmxaya48c2588uv9kkux8.png" alt="Diagrama de classe"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Teremos o modelo &lt;code&gt;Player&lt;/code&gt; com o atributo nome que representará o jogador da comunidade. O modelo &lt;code&gt;Trophy&lt;/code&gt; servirá para representar os troféus que os jogadores poderão obter e terá o atributo de pontuação, que é o valor a ser contabilizado na classificação geral. Por exemplo:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1º Lugar no Torneio Semanal: 100 pontos
2º Lugar - Torneio Semanal: 50 pontos
1º Lugar - Torneio Mensal: 500 pontos
2º Lugar - Torneio Mensal:  250 pontos
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;E por fim, teremos o modelo &lt;code&gt;Achievement&lt;/code&gt;, que servirá para registrar as conquistas dos jogadores, ou seja, nele será representado o jogador e qual troféu ele ganhou.&lt;/p&gt;

&lt;p&gt;Para criar essa estrutura que descrevi, crie uma nova aplicação Rails e execute esses comandos no terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;bundle&lt;/span&gt; &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="n"&gt;rails&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="n"&gt;player&lt;/span&gt; &lt;span class="nb"&gt;name&lt;/span&gt;
&lt;span class="n"&gt;bundle&lt;/span&gt; &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="n"&gt;rails&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="n"&gt;trophy&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="n"&gt;points&lt;/span&gt;&lt;span class="ss"&gt;:integer&lt;/span&gt;
&lt;span class="n"&gt;bundle&lt;/span&gt; &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="n"&gt;rails&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="n"&gt;achievement&lt;/span&gt; &lt;span class="n"&gt;player&lt;/span&gt;&lt;span class="ss"&gt;:references&lt;/span&gt; &lt;span class="n"&gt;trophy&lt;/span&gt;&lt;span class="ss"&gt;:references&lt;/span&gt;
&lt;span class="n"&gt;bundle&lt;/span&gt; &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="n"&gt;rails&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="ss"&gt;:create&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="ss"&gt;:migrate&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Agora vamos preencher com alguns dados falsos para termos com o que testar. No arquivo &lt;code&gt;db/seeds.rb&lt;/code&gt; coloque o seguinte código e depois execute um &lt;code&gt;bundle exec rails db:seed&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Players&lt;/span&gt;
&lt;span class="n"&gt;player_a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s1"&gt;'Jogador A'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;player_b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s1"&gt;'Jogador B'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;player_c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s1"&gt;'Jogador C'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;player_d&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s1"&gt;'Jogador D'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Trophies&lt;/span&gt;
&lt;span class="n"&gt;tropy_gold_estadual&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Trophy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="ss"&gt;title: &lt;/span&gt;&lt;span class="s1"&gt;'Ouro - Torneio Estadual'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;points: &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;tropy_silver_estadual&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Trophy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="ss"&gt;title: &lt;/span&gt;&lt;span class="s1"&gt;'Prata - Torneio Estadual'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;points: &lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;tropy_gold_nacional&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Trophy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="ss"&gt;title: &lt;/span&gt;&lt;span class="s1"&gt;'Ouro - Torneio Nacional'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;points: &lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;tropy_silver_nacional&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Trophy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="ss"&gt;title: &lt;/span&gt;&lt;span class="s1"&gt;'Prata - Torneio Nacional'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;points: &lt;/span&gt;&lt;span class="mi"&gt;300&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Player A achievements&lt;/span&gt;
&lt;span class="c1"&gt;# 2x Gold Nacional&lt;/span&gt;
&lt;span class="c1"&gt;# 2x Gold Estadual&lt;/span&gt;
&lt;span class="c1"&gt;# Total: 1200 points&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="n"&gt;tropy_gold_estadual&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
  &lt;span class="n"&gt;tropy_gold_estadual&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
  &lt;span class="n"&gt;tropy_gold_nacional&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;tropy_gold_nacional&lt;/span&gt;
&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;trophy&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="no"&gt;Achievement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="ss"&gt;player: &lt;/span&gt;&lt;span class="n"&gt;player_a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;trophy: &lt;/span&gt;&lt;span class="n"&gt;trophy&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# Player B achievements&lt;/span&gt;
&lt;span class="c1"&gt;# 1x Gold Estadual&lt;/span&gt;
&lt;span class="c1"&gt;# 1x Silver Estadual&lt;/span&gt;
&lt;span class="c1"&gt;# Total: 150 points&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt; 
  &lt;span class="n"&gt;tropy_gold_estadual&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
  &lt;span class="n"&gt;tropy_silver_estadual&lt;/span&gt;
&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;trophy&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="no"&gt;Achievement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="ss"&gt;player: &lt;/span&gt;&lt;span class="n"&gt;player_b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;trophy: &lt;/span&gt;&lt;span class="n"&gt;trophy&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# Player C achievements&lt;/span&gt;
&lt;span class="c1"&gt;# 3x Silver Estadual&lt;/span&gt;
&lt;span class="c1"&gt;# Total: 150 points&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="n"&gt;tropy_silver_estadual&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
  &lt;span class="n"&gt;tropy_silver_estadual&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
  &lt;span class="n"&gt;tropy_silver_estadual&lt;/span&gt;
&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;trophy&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="no"&gt;Achievement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="ss"&gt;player: &lt;/span&gt;&lt;span class="n"&gt;player_c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;trophy: &lt;/span&gt;&lt;span class="n"&gt;trophy&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# Player D achievements&lt;/span&gt;
&lt;span class="c1"&gt;# 1x Gold Nacional&lt;/span&gt;
&lt;span class="c1"&gt;# 1x Silver Nacional&lt;/span&gt;
&lt;span class="c1"&gt;# 1x Gold Estadual&lt;/span&gt;
&lt;span class="c1"&gt;# Total: 850 points&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="n"&gt;tropy_gold_nacional&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
  &lt;span class="n"&gt;tropy_silver_nacional&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
  &lt;span class="n"&gt;tropy_silver_estadual&lt;/span&gt;
&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;trophy&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="no"&gt;Achievement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="ss"&gt;player: &lt;/span&gt;&lt;span class="n"&gt;player_d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;trophy: &lt;/span&gt;&lt;span class="n"&gt;trophy&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Problema
&lt;/h2&gt;

&lt;p&gt;Com nossa estrutura montada e com alguns dados fictícios criados, nos deparamos com a necessidade de mostrar um ranking, uma classificação geral de todos os jogadores da comunidade em uma tela. Para resolver essa consulta com a API do ActiveRecord, poderia ser feito algo assim:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;players&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Player&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&amp;lt;-&lt;/span&gt;&lt;span class="no"&gt;SQL&lt;/span&gt;&lt;span class="sh"&gt;
      players.name, 
      sum(trophies.points) as total_points
&lt;/span&gt;&lt;span class="no"&gt;    SQL&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;joins&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;achievements: :trophy&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'players.id'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'total_points DESC'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;players&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;player&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;
  &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;total_points&lt;/span&gt;
  &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s1"&gt;'----'&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# No console apareceria:&lt;/span&gt;
&lt;span class="c1"&gt;# Jogador A&lt;/span&gt;
&lt;span class="c1"&gt;# 1200&lt;/span&gt;
&lt;span class="c1"&gt;# ----&lt;/span&gt;
&lt;span class="c1"&gt;# Jogador D&lt;/span&gt;
&lt;span class="c1"&gt;# 850&lt;/span&gt;
&lt;span class="c1"&gt;# ----&lt;/span&gt;
&lt;span class="c1"&gt;# Jogador B&lt;/span&gt;
&lt;span class="c1"&gt;# 150&lt;/span&gt;
&lt;span class="c1"&gt;# ----&lt;/span&gt;
&lt;span class="c1"&gt;# Jogador C&lt;/span&gt;
&lt;span class="c1"&gt;# 150&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Perceba que esse código faz um select utilizando &lt;code&gt;SUM&lt;/code&gt;, 2 &lt;code&gt;joins&lt;/code&gt;, um &lt;code&gt;group&lt;/code&gt; e uma ordenação. Não é lá um código muito bonito de se ver espalhado pela aplicação e também não é muito fácil manter. Supondo que a comunidade de jogadores seja enorme e precise fazer uma paginação, ou a consulta evolua para algo mais personalizado que precise de algum filtro, esse código pode ficar mais complexo ainda. Para ajudar a organizar isso, vamos transferir essa consulta para uma view do banco de dados.&lt;/p&gt;

&lt;h2&gt;
  
  
  Instalando a gem scenic
&lt;/h2&gt;

&lt;p&gt;Para ajudar a gerenciar as views sql, vou utilizar a gem &lt;a href="https://github.com/scenic-views/scenic" rel="noopener noreferrer"&gt;scenic&lt;/a&gt;. Ela traz a vantagem de poder utilizar as views sem a necessidade de alterar o formato do schema.rb para structure.sql. De quebra fornece um versionamento dessas views facilitando o acompanhamento das modificações e as reversões pra versões anteriores. Outro ponto positivo é que todo o código da estrutura da view é armazenado em arquivos .sql, facilitando o realce da sintaxe e a execução do código em terminais ou outras ferramentas de banco de dados.&lt;/p&gt;

&lt;p&gt;Para instalar a scenic no nosso projeto, primeiro adicione &lt;code&gt;gem 'scenic'&lt;/code&gt; no arquivo &lt;code&gt;Gemfile&lt;/code&gt; e depois execute um &lt;code&gt;bundle install&lt;/code&gt; para finalizar a instalação.&lt;/p&gt;

&lt;h2&gt;
  
  
  Criando a view no banco de dados
&lt;/h2&gt;

&lt;p&gt;Para gerar uma view com a scenic é necessário executar o comando no console:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;bundle&lt;/span&gt; &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="n"&gt;rails&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt; &lt;span class="n"&gt;scenic&lt;/span&gt;&lt;span class="ss"&gt;:view&lt;/span&gt; &lt;span class="n"&gt;ranking_items&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Esse comando cria um arquivo na pasta &lt;code&gt;db/migrations&lt;/code&gt; chamado de &lt;code&gt;[TIMESTAMP]_create_ranking_items.rb&lt;/code&gt; com o seguinte conteúdo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CreateRankingItems&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Migration&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;6.1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;change&lt;/span&gt;
    &lt;span class="n"&gt;create_view&lt;/span&gt; &lt;span class="ss"&gt;:ranking_items&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;E também é criado o arquivo &lt;code&gt;db/views/ranking_items_v01.sql&lt;/code&gt; sem nada escrito nele. É nele que vamos colocar o código que cria nossa view:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
  &lt;span class="n"&gt;players&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;player_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;players&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;player_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;trophies&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;points&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;total_points&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;
  &lt;span class="n"&gt;players&lt;/span&gt;
&lt;span class="k"&gt;INNER&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt;
  &lt;span class="n"&gt;achievements&lt;/span&gt; 
  &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;achievements&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;player_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;players&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;
&lt;span class="k"&gt;INNER&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt;
  &lt;span class="n"&gt;trophies&lt;/span&gt;
  &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;trophies&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;achievements&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;trophy_id&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt;
  &lt;span class="n"&gt;players&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt;
  &lt;span class="k"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;trophies&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;points&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Perceba que não coloquei nenhum &lt;code&gt;CREATE OR REPLACE VIEW&lt;/code&gt;, que são os comandos SQL relacionados a criação ou substuição da view, pois a scenic já entende que uma view deve ser criada com o nome ranking_items através do &lt;code&gt;create_view :ranking_items&lt;/code&gt; que está na migração gerada.&lt;/p&gt;

&lt;p&gt;Para finalizar e criar a view no banco de dados execute um &lt;code&gt;bundle exec rails db:migrate&lt;/code&gt;. Com isso, se você for no terminal psql ou no seu SGBD poderá conferir o resultado que a view está gerando com a consulta &lt;code&gt;SELECT * FROM ranking_items&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faq9vbmyy5kp32p86e2qw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faq9vbmyy5kp32p86e2qw.png" alt="Resultado da consulta"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Criando o modelo
&lt;/h2&gt;

&lt;p&gt;Agora é a hora de mapear a view que criamos no banco de dados para um modelo do ActiveRecord. Não é muito diferente de criar um modelo que aponta para uma tabela física mas tem algumas diferenças. Na pasta &lt;code&gt;app/models&lt;/code&gt; crie um arquivo chamado &lt;code&gt;ranking_item.rb&lt;/code&gt; e coloque o seguinte conteúdo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RankingItem&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="kp"&gt;protected&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;readonly?&lt;/span&gt;
    &lt;span class="kp"&gt;true&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;É necessário sobrescrever o método protegido &lt;code&gt;readonly?&lt;/code&gt; para informar ao Rails e aos demais desenvolvedores que utilizando esse modelo não é permitido inserir, atualizar ou apagar informações no banco de dados, e se alguém tentar fazer essas ações receberá a exceção avisando &lt;code&gt;ActiveRecord::ReadOnlyRecord (RankingItem is marked as readonly)&lt;/code&gt;. Sem essa configuração, os métodos create, update, save, destroy e afins, lançarão erros de sintaxe do SQL não muito claros.&lt;/p&gt;

&lt;p&gt;Antes de prosseguir, só uma dica útil para quem não quer ou não pode seguir o padrão de nomeação das views de acordo com o nome do modelo. Existe a possibilidade de configurar o modelo para informar qual o nome da view que ele deve buscar os dados. Por exemplo, supondo que sua view teve que ser criada com o nome &lt;code&gt;dbviews_0001_ranking&lt;/code&gt;, você terá que fazer assim no modelo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RankingItem&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;table_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="ss"&gt;:dbviews_0001_ranking&lt;/span&gt;
  &lt;span class="c1"&gt;# ... resto do código&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pronto, agora se abrir o console do rails com o &lt;code&gt;bundle exec rails c&lt;/code&gt;, o modelo poderá ser utilizado normalmente, boa parte da API do ActiveRecord será compatível:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;RankingItem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; #&amp;lt;RankingItem player_id: 9, player_name: "Jogador A", total_points: 1200&amp;gt;&lt;/span&gt;

&lt;span class="no"&gt;RankingItem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; #&amp;lt;ActiveRecord::Relation [#&amp;lt;RankingItem ...&amp;gt;]&amp;gt;&lt;/span&gt;

&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;RankingItem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;player_name: &lt;/span&gt;&lt;span class="s1"&gt;'Jogador A'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;total_points&lt;/span&gt; 
&lt;span class="c1"&gt;# =&amp;gt; 1200&lt;/span&gt;

&lt;span class="no"&gt;RankingItem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'total_points &amp;gt; 800'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;size&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; 2&lt;/span&gt;

&lt;span class="no"&gt;RankingItem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; #&amp;lt;ActiveRecord::Relation [#&amp;lt;RankingItem ...&amp;gt;]&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Outra vantagem é que posso também usar as associações do Rails para integrar com outros modelos. Por exemplo, um &lt;code&gt;ranking_item&lt;/code&gt; pertence a um &lt;code&gt;player&lt;/code&gt;. É totalmente possível adicionar um &lt;code&gt;belongs_to :player&lt;/code&gt; na classe &lt;code&gt;RankingItem&lt;/code&gt; e acessar o jogador dessa forma:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;RankingItem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;
&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;player&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; #&amp;lt;Player id: 9, name: "Jogador A", created_at: ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Quase pronto, mas acho que seria bom mostrar a posição do jogador na classificação e percebi que a view que criei não foi criada com essa coluna. Como fazer isso?&lt;/p&gt;

&lt;h2&gt;
  
  
  Modificando views já existentes
&lt;/h2&gt;

&lt;p&gt;A scenic também auxilia quando uma view precisa ter sua estrutura alterada. O comando é o mesmo que é utilizado para criar a view, ou seja: &lt;code&gt;bundle exec rails g scenic:view ranking_items&lt;/code&gt;. Isso gerará um arquivo de migração chamado &lt;code&gt;[TIMESTAMP]_update_ranking_items_to_version_2.rb&lt;/code&gt; com o seguinte conteúdo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UpdateRankingItemsToVersion2&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Migration&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;6.1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;change&lt;/span&gt;
    &lt;span class="n"&gt;update_view&lt;/span&gt; &lt;span class="ss"&gt;:ranking_items&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;version: &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;revert_to_version: &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A diferença aqui é que ao invés do &lt;code&gt;create_view&lt;/code&gt; que foi gerado na primeira vez que executamos o comando, agora tem um &lt;code&gt;update_view&lt;/code&gt;, a próxima versão que deverá ser utilizada quando a migração for executada e qual a versão deverá voltar em caso de reversão dessa migração. Esse &lt;code&gt;update_view&lt;/code&gt; fará um &lt;code&gt;DROP VIEW&lt;/code&gt; e criará uma nova já atualizada, mas nem sempre é possível por conta de dependências entre views. Se um dia passar por essa situação e desejar utilizar um &lt;code&gt;CREATE OR REPLACE VIEW&lt;/code&gt;, troque o &lt;code&gt;update_view&lt;/code&gt; por &lt;code&gt;replace_view&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;O comando também gerou o arquivo &lt;code&gt;db/views/ranking_items_v02.sql&lt;/code&gt; já preenchido com o código da primeira versão. Na nova versão, vou adicionar a coluna rank utilizando a função &lt;code&gt;RANK&lt;/code&gt; do SQL que representará a posição do jogador, e também deixarei de ordenar pela soma dos pontos e passarei a ordenar pela nova coluna &lt;code&gt;rank&lt;/code&gt;. A v2 ficará assim:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
  &lt;span class="n"&gt;RANK&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="k"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;trophies&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;points&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;rank&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;players&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;player_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;players&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;player_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;trophies&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;points&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;total_points&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;
  &lt;span class="n"&gt;players&lt;/span&gt;
&lt;span class="k"&gt;INNER&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt;
  &lt;span class="n"&gt;achievements&lt;/span&gt;
  &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;achievements&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;player_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;players&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;
&lt;span class="k"&gt;INNER&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt;
  &lt;span class="n"&gt;trophies&lt;/span&gt;
  &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;trophies&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;achievements&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;trophy_id&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt;
  &lt;span class="n"&gt;players&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt;
  &lt;span class="n"&gt;rank&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Execute o &lt;code&gt;bundle exec rails db:migrate&lt;/code&gt; e você terá sua view atualizada. Se fizer o select na tabela verá o novo campo com a posição do jogador:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frer7s5mrfxfavs9d5lbi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frer7s5mrfxfavs9d5lbi.png" alt="Resultado da consulta"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Mas e se a consulta da nossa view fosse tão complexa, em tabelas enormes quantidades de registros que não fosse possível utilizar uma view comum e fosse necessária uma view materializada? O scenic também tem opções pra esses casos.&lt;/p&gt;

&lt;h2&gt;
  
  
  Materializando a view
&lt;/h2&gt;

&lt;p&gt;Caso você precise de uma view materializada, os comandos para gerá-la são os mesmos que eu passei até agora, com a diferença que a flag &lt;code&gt;—-materialized&lt;/code&gt; deverá estar presente. Por exemplo: &lt;code&gt;rails g scenic:view nome_da_view --materialized&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Para atualizar os dados da view materializada a scenic disponibiliza o método &lt;code&gt;Scenic.database.refresh_materialized_view&lt;/code&gt;. Então é possível você criar um método de classe no seu modelo para facilitar o uso, fica assim:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MeuModelo&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;refresh&lt;/span&gt;
    &lt;span class="no"&gt;Scenic&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;database&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;refresh_materialized_view&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s1"&gt;'nome_da_view_materializada'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
        &lt;span class="ss"&gt;concurrently: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
        &lt;span class="ss"&gt;cascade: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="kp"&gt;protected&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;readonly?&lt;/span&gt;
    &lt;span class="kp"&gt;true&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusão
&lt;/h2&gt;

&lt;p&gt;É isso, apresentei mais uma solução que vale sempre a pena ter em mãos para quando situações como as apresentadas surgirem no dia a dia. Lembrando sempre, que nem sempre essa é a melhor opção para solucionar todos os problemas, às vezes faz mais sentido criar uma tabela física mesmo ou então usar o padrão Query Objects que falarei posteriormente sobre ele aqui no blog. &lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>sql</category>
      <category>views</category>
    </item>
    <item>
      <title>Configurando o Rails, RSpec e Rubocop no Github Actions</title>
      <dc:creator>Stephann</dc:creator>
      <pubDate>Fri, 25 Mar 2022 23:19:45 +0000</pubDate>
      <link>https://dev.to/stephann/configurando-o-rails-rspec-e-rubocop-no-github-actions-dgc</link>
      <guid>https://dev.to/stephann/configurando-o-rails-rspec-e-rubocop-no-github-actions-dgc</guid>
      <description>&lt;h2&gt;
  
  
  Introdução
&lt;/h2&gt;

&lt;p&gt;A integração contínua, ou CI (continuous integration) é a estratégia de compilar e testar a aplicação toda vez que um desenvolvedor desejar integrar suas modificações ao repositório central. Essa estratégia ajuda a detectar falhas tanto no código enviado, como no encaixe desse novo código ao código já existente, de forma mais precoce dentro do fluxo de desenvolvimento. No CI podem ser executados testes automatizados, analisadores de estilo e de organização, escaneadores de vulnerabilidades e qualquer coisa que possa ser automatizada e ajude o time de desenvolvimento ter uma aplicação mais segura, organizada ou com bom desempenho.&lt;/p&gt;

&lt;p&gt;Uma das ferramentas disponíveis para implementar o CI, é o Github Actions, um produto disponibilizado dentro do ecossistema do Github e que se integra perfeitamente dentro da página dos repositórios sem nenhuma fricção de ter que ficar trocando de serviço para acompanhar o andamento das execuções, ou de ter que configurar o acesso de outro serviço aos projetos. Nesse artigo vou explicar como configurar o Github Actions em uma aplicação Rails, executando o RSpec (um framework de testes)  e o Rubocop (um analisador de código Ruby).&lt;/p&gt;

&lt;h2&gt;
  
  
  Criando o workflow
&lt;/h2&gt;

&lt;p&gt;No fim do artigo eu coloquei o arquivo YAML completo para quem quiser apenas copiar e colar e adequar para sua realidade. Aqui eu vou explicar o que significa cada configuração para explicar os porquês de cada linha e suas alternativas.&lt;/p&gt;

&lt;p&gt;O primeiro passo é criar uma pasta na raíz do repositório chamada .github e adicionar a pasta workflows dentro dela, pois é nessa pasta que o Github detecta que o projeto tem fluxos a serem executados. Depois crie um arquivo YAML dentro de .github/workflows e nomeie com algo que indique a natureza do fluxo que será executado. Pro caso de testes e análise de código, eu geralmente nomeio como ci.yml ou test_and_code_analysis.yml.&lt;/p&gt;

&lt;p&gt;A estrutura básica do YAML que o Github Actions aceita é basicamente essa:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;CI&lt;/span&gt; &lt;span class="c1"&gt;# Nome do fluxo&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;push&lt;/span&gt; &lt;span class="c1"&gt;# Evento que dispara o fluxo&lt;/span&gt;
&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;rubocop&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;# Identificador do job&lt;/span&gt;
    &lt;span class="s"&gt;...&lt;/span&gt; &lt;span class="c1"&gt;# Configurações do job&lt;/span&gt;
  &lt;span class="na"&gt;rspec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;# Identificador do job&lt;/span&gt;
    &lt;span class="s"&gt;...&lt;/span&gt; &lt;span class="c1"&gt;# Opçòes de configuração do job&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Logo mais vou explicar como configurar cada um dos jobs, mas primeiro, atenção para o &lt;code&gt;on: push&lt;/code&gt;, nele é colocado o evento que fará o fluxo ser disparado. No meu caso, eu coloco push, pois quero que o CI seja executado toda vez que alguém fizer um git push origin nome_do_branch. Mas há a opção de executar apenas quando um pull request for criado, que é a configuração &lt;code&gt;on: pull_request&lt;/code&gt;. Para outras opções de evento, há uma lista bem detalhada na &lt;a href="https://docs.github.com/en/actions/reference/events-that-trigger-workflows"&gt;documentação oficial do Github&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configurando o Rubocop
&lt;/h2&gt;

&lt;p&gt;Deixando claro que essa parte é opcional,  pois caso tu não tenha necessidade ou possibilidade de ter uma análise de código no projeto, é só seguir o artigo sem adicionar os códigos referentes ao Rubocop e aproveitar apenas os trechos que mostro como configurar a execução dos testes.&lt;/p&gt;

&lt;p&gt;Voltando ao &lt;code&gt;.github/workflows/ci.yml&lt;/code&gt; para definir o fluxo de análise de código:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="nn"&gt;...&lt;/span&gt;
&lt;span class="na"&gt;rubocop&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Rubocop&lt;/span&gt;
  &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt; &lt;span class="c1"&gt;# Informa o SO que será usado nos testes&lt;/span&gt;
  &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Pega o código do projeto&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout code&lt;/span&gt;
      &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v1&lt;/span&gt;

    &lt;span class="c1"&gt;# Configura o Ruby e instala as dependências&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Setup Ruby&lt;/span&gt;
      &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ruby/setup-ruby@v1&lt;/span&gt;
      &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;bundler-cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
        &lt;span class="c1"&gt;# Exemplo de outras configurações:&lt;/span&gt;
        &lt;span class="c1"&gt;# ruby-version: 3.0.0&lt;/span&gt;
        &lt;span class="c1"&gt;# bundler: 2.2.8&lt;/span&gt;
        &lt;span class="c1"&gt;# working-directory: ./backend&lt;/span&gt;

    &lt;span class="c1"&gt;# Executa o Rubocop&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Analyze code&lt;/span&gt;
      &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bundle exec rubocop&lt;/span&gt;
&lt;span class="nn"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;É um fluxo simples, com 3 passos: Pegar o código, instalar o ruby e as dependências e executar o Rubocop. Antes de seguir, vale a pena me aprofundar nesse passo de instalação do Ruby para explicar melhor o que está acontecendo. Antes, o modo "oficial" de instalar o Ruby num fluxo, era utilizando a action criada pela equipe do Github, a &lt;a href="https://github.com/actions/setup-ruby"&gt;actions/setup-ruby&lt;/a&gt;,  mas ela foi depreciada recentemente, e agora é sugerido utilizar a &lt;a href="https://github.com/ruby/setup-ruby"&gt;ruby/setup-ruby&lt;/a&gt;, uma action mantida pelo time do Ruby. Ela tem algumas configurações importantes que podem ser passadas dentro do &lt;code&gt;with:&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ruby-version&lt;/code&gt;:&lt;br&gt;
Essa opção permite que seja informada a versão do ruby que será instalada. Caso não seja definida, será detectada a versão definida no arquivo &lt;code&gt;.ruby-version&lt;/code&gt; ou no &lt;code&gt;.tool-versions&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;bundler&lt;/code&gt;:&lt;br&gt;
Essa opção serve para definir a versão do bundler que será utilizada para instalar as dependências. Se não estiver definida, a versão será detectada de acordo com o &lt;code&gt;BUNDLED_WITH&lt;/code&gt; do &lt;code&gt;Gemfile.lock&lt;/code&gt;, e se não encontrar, será utilizada a versão mais recente.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;working-directory&lt;/code&gt;:&lt;br&gt;
Caso o repositório tenha uma organização de pastas diferenciada, e a aplicação Rails não esteja na raíz, é necessário informar o caminho onde estão os arquivos &lt;code&gt;.ruby-version&lt;/code&gt;, &lt;code&gt;.tool-versions&lt;/code&gt; e &lt;code&gt;Gemfile.lock&lt;/code&gt;, assim as versões do Ruby e do bundler poderão ser detectadas. No caso da aplicação que trabalho, tenho que fazer: &lt;code&gt;working-directory: ./services/catarse&lt;/code&gt;, para o fluxo funcionar corretamente.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;bundler-cache&lt;/code&gt;:&lt;br&gt;
Se essa opção estiver como true, a action irá instalar as dependências com um bundle install e fará o cache das gems de forma automática, para não ter que instalar todo o conjunto de dependências a cada nova execução do fluxo.&lt;/p&gt;

&lt;p&gt;Agora note que não configurei nada de PostgreSQL, ou NodeJS, porque a análise de código do Rubocop não necessita dessas ferramentas, apenas do Ruby e das gems da família Rubocop.&lt;/p&gt;
&lt;h2&gt;
  
  
  Configurando o RSpec
&lt;/h2&gt;

&lt;p&gt;Agora o principal, que é a execução dos testes. Dessa vez terá uns passos a mais, mas também nada muito complexo. Primeiro vou deixar a configuração inteira e vou explicando aos poucos parte por parte:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;rubocop&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="s"&gt;...&lt;/span&gt;
&lt;span class="na"&gt;rspec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;RSpec&lt;/span&gt;
  &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rubocop&lt;/span&gt;
  &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-20.04&lt;/span&gt;
  &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;RAILS_ENV&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;test&lt;/span&gt;
    &lt;span class="na"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres://postgres:example@localhost:5432/db_test&lt;/span&gt;
  &lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;postgres&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres:latest&lt;/span&gt;
      &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;5432:5432'&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;POSTGRES_DB&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;db_test&lt;/span&gt;
        &lt;span class="na"&gt;POSTGRES_USER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres&lt;/span&gt;
        &lt;span class="na"&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;example&lt;/span&gt;
      &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;--health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;
  &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout code&lt;/span&gt;
      &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v1&lt;/span&gt;

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Setup Ruby&lt;/span&gt;
      &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ruby/setup-ruby@v1&lt;/span&gt;
      &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;bundler-cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install postgres client dependencies&lt;/span&gt;
      &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sudo apt-get install libpq-dev&lt;/span&gt;

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Setup Node&lt;/span&gt;
      &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-node@v1&lt;/span&gt;
      &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;12.20.0&lt;/span&gt;

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Yarn package cache&lt;/span&gt;
      &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/cache@v2&lt;/span&gt;
      &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./node_modules&lt;/span&gt;
        &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ runner.os }}-yarn-v1-${{ hashFiles('./yarn.lock') }}&lt;/span&gt;

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install Yarn packages&lt;/span&gt;
      &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;yarn install --pure-lockfile&lt;/span&gt;

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Create database&lt;/span&gt;
      &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
        &lt;span class="s"&gt;bundle exec rails db:create&lt;/span&gt;
        &lt;span class="s"&gt;# Se o projeto usar `schema.rb`&lt;/span&gt;
        &lt;span class="s"&gt;bundle exec rails db:schema:load&lt;/span&gt;
        &lt;span class="s"&gt;# Se o projeto usar `structure.sql`&lt;/span&gt;
        &lt;span class="s"&gt;bundle exec rails db:structure:load&lt;/span&gt;
        &lt;span class="s"&gt;# Se o projeto não usar nenhum dos dois&lt;/span&gt;
        &lt;span class="s"&gt;bundle exec rails db:migrate&lt;/span&gt;

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run tests&lt;/span&gt;
      &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bundle exec rspec spec&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Começando primeiro por aqui:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;rspec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;RSpec&lt;/span&gt;
  &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rubocop&lt;/span&gt;
  &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-20.04&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;É bem parecido com o início da definição do fluxo do Rubocop, mas com um parâmetro a mais, o &lt;code&gt;needs&lt;/code&gt;. Essa configuração serve para avisar ao Github  que o RSpec só poderá ser executado depois da execução bem sucedida do Rubocop, ou seja, se o código estiver fora do padrão do time, os testes nem serão executados. Mas caso queira que o RSpec e o Rubocop sejam executados paralelamente, só remover essa opção, fica ao critério da equipe. Agora a configuração do banco que dados:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;RAILS_ENV&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;test&lt;/span&gt;
  &lt;span class="na"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres://postgres:example@localhost:5432/db_test&lt;/span&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;postgres&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres:latest&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;5432:5432'&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_DB&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;db_test&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_USER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;example&lt;/span&gt;
    &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;--health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No &lt;code&gt;env.DATABASE_URL&lt;/code&gt;, eu defini uma URL de conexão com o banco de dados que o Rails utilizará quando for criar a estrutura das tabelas da aplicação. Essa URL é baseada nas configurações que passei na criação do serviço postgres que será necessário para a execução dos testes. Informei a imagem sendo &lt;code&gt;postgres:lastest&lt;/code&gt; que pegará o PostgreSQL mais recente, mas pode ser utilizada qualquer versão, por exemplo: postgres:12. Dentro de &lt;code&gt;services.postgres.options&lt;/code&gt; está alguns comandos para aguardar o servidor de banco de dados subir, senão corre o risco do fluxo seguir e não ser possível se conectar no banco quando for necessário por conta do PostgreSQL não estar pronto.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout code&lt;/span&gt;
      &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v1&lt;/span&gt;

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Setup Ruby&lt;/span&gt;
      &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ruby/setup-ruby@v1&lt;/span&gt;
      &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;bundler-cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install postgres client dependencies&lt;/span&gt;
      &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sudo apt-get install libpq-dev&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Os dois primeiros passos são idênticos ao dois primeiros do fluxo de análise de código, então volta lá na seção anterior caso tenha esquecido do que se trata. No terceiro, é instalado uma dependência de desenvolvimento do PostgreSQL, que é necessária para rodar os comando de banco de dados.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Setup Node&lt;/span&gt;
    &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-node@v1&lt;/span&gt;
    &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;12.20.0&lt;/span&gt;

  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Yarn package cache&lt;/span&gt;
    &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/cache@v2&lt;/span&gt;
    &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./node_modules&lt;/span&gt;
      &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ runner.os }}-yarn-v1-${{ hashFiles('./yarn.lock') }}&lt;/span&gt;

  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install Yarn packages&lt;/span&gt;
    &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;yarn install --pure-lockfile&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Para projetos que utilizam o Webpacker às vezes é necessário instalar as dependências de front-end para que os testes sejam executados. No primeiro passo está sendo instalado o Node 12.20, no passo seguinte é utilizado o &lt;code&gt;actions/cache&lt;/code&gt; que serve tanto para recuperar um cache quanto, se for necessário, para criar um novo cache no final do fluxo. A pasta que está sendo armazenada para ser reaproveitada nas execuções seguintes é a &lt;code&gt;node_modules&lt;/code&gt; que geralmente fica na raíz do projeto, e será o conteúdo do &lt;code&gt;yarn.lock&lt;/code&gt; que o github saberá se será necessário instalar dependências ou apenas reutilizar a node_modules existente. E no fim o &lt;code&gt;yarn install&lt;/code&gt; é utilizado para fazer essa instalação dos pacotes, com a opção &lt;code&gt;—pure-lockfile&lt;/code&gt; para que não seja gerado um yarn.lock novo.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Create database&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;bundle exec rails db:create&lt;/span&gt;
    &lt;span class="s"&gt;# Se o projeto usar `schema.rb`&lt;/span&gt;
    &lt;span class="s"&gt;bundle exec rails db:schema:load&lt;/span&gt;
    &lt;span class="s"&gt;# Se o projeto usar `structure.sql`&lt;/span&gt;
    &lt;span class="s"&gt;bundle exec rails db:structure:load&lt;/span&gt;
    &lt;span class="s"&gt;# Se o projeto não usar nenhum dos dois&lt;/span&gt;
    &lt;span class="s"&gt;bundle exec rails db:migrate&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run tests&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bundle exec rspec spec&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Por fim, no "Create database", o banco de testes é criado e a sua estrutura é montada a partir do &lt;code&gt;schema.rb&lt;/code&gt; ou &lt;code&gt;structure.sql&lt;/code&gt; e, caso esses arquivos não existam, as migrações serão executadas. Uma outra opção que substitui esse trecho da criação do banco de dados e sua estrutura é utilizar o comando &lt;code&gt;rails db:test:prepare&lt;/code&gt;.&lt;br&gt;
Após o banco de dados pronto, a execução dos testes é realizada com o &lt;code&gt;bundle exec rspec&lt;/code&gt; spec e esse é o ponto final do fluxo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusão
&lt;/h2&gt;

&lt;p&gt;Agora com tudo configurado, todo push que for feito ao projeto dispará uma execução do CI no Github Actions, que pode ser acompanhada na aba "Actions" dentro do projeto no Github, e após o fim da execução, será adicionado um "check" verdinho ou um "x" vermelho ao lado do commit, e dentro do pull request para indicar o resultado da execução.&lt;/p&gt;

&lt;p&gt;Para projetos de código aberto, o Github Actions é gratuito, já para projetos privados de usuários não pagantes, são disponibilizados 2.000 minutos de execução mensalmente. Não há desculpa para não ter CI no projeto.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;CI&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;push&lt;/span&gt;
&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;rubocop&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Rubocop&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-20.04&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout code&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v1&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Setup Ruby&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ruby/setup-ruby@v1&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;bundler-cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Analyze code&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bundle exec rubocop&lt;/span&gt;

  &lt;span class="na"&gt;rspec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;RSpec&lt;/span&gt;
    &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rubocop&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-20.04&lt;/span&gt;
    &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;RAILS_ENV&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;test&lt;/span&gt;
      &lt;span class="na"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres://postgres:example@localhost:5432/db_test&lt;/span&gt;
    &lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;postgres&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres:latest&lt;/span&gt;
        &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;5432:5432'&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;POSTGRES_DB&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;db_test&lt;/span&gt;
          &lt;span class="na"&gt;POSTGRES_USER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres&lt;/span&gt;
          &lt;span class="na"&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;example&lt;/span&gt;
        &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;--health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout code&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v1&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Setup Ruby&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ruby/setup-ruby@v1&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;bundler-cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install postgres client dependencies&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sudo apt-get install libpq-dev&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Setup Node&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-node@v1&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;12.20.0&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Yarn package cache&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/cache@v2&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./node_modules&lt;/span&gt;
          &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ runner.os }}-yarn-v1-${{ hashFiles('./yarn.lock') }}&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install Yarn packages&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;yarn install --pure-lockfile&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Create database&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;bundle exec rails db:create&lt;/span&gt;
          &lt;span class="s"&gt;# Se o projeto usar `schema.rb`&lt;/span&gt;
          &lt;span class="s"&gt;bundle exec rails db:schema:load&lt;/span&gt;
          &lt;span class="s"&gt;# Se o projeto usar `structure.sql`&lt;/span&gt;
          &lt;span class="s"&gt;bundle exec rails db:structure:load&lt;/span&gt;
          &lt;span class="s"&gt;# Se o projeto não usar nenhum dos dois&lt;/span&gt;
          &lt;span class="s"&gt;bundle exec rails db:migrate&lt;/span&gt;

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run tests&lt;/span&gt;
      &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bundle exec rspec spec&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>rails</category>
      <category>ruby</category>
      <category>github</category>
      <category>ci</category>
    </item>
    <item>
      <title>Fazendo deploy em produção com Rails, PostgreSQL e Dokku</title>
      <dc:creator>Stephann</dc:creator>
      <pubDate>Thu, 24 Mar 2022 01:03:06 +0000</pubDate>
      <link>https://dev.to/stephann/fazendo-deploy-em-producao-com-rails-postgresql-e-dokku-ic7</link>
      <guid>https://dev.to/stephann/fazendo-deploy-em-producao-com-rails-postgresql-e-dokku-ic7</guid>
      <description>&lt;h2&gt;
  
  
  Introdução
&lt;/h2&gt;

&lt;p&gt;Disponibilizar uma aplicação na internet para outras pessoas acessarem é uma tarefa razoavelmente simples caso utilizemos o Heroku como PaaS (Plataforma como Serviço), mas se torna uma tarefa um pouco mais complicada quando, querendo baixar custos, passamos a utilizar uma instância na DigitalOcean, Linode, Vultr e etc, e é necessário configurar tudo manualmente, principalmente pra mim, que assunto de infraestrutura eu sou quase um leigo. Mas ainda há esperança para desenvolvedores como eu, que da nomeclatura DevOps, tem só o Dev, e nada de Ops. Eu falo do Dokku, que une a facilitade do Heroku com os baixos custos de uma instância própria. Ele é uma ferramenta de código aberto e gratuita, que resumidamente é um "self-hosted Heroku", um PaaS que tu hospeda no teu próprio servidor, te proporcionando uma maior capacidade de customização e uma redução dos custos.&lt;/p&gt;

&lt;p&gt;A seguir vou mostrar como instalar o Dokku numa instância da DigitalOcean (mas tu consegue reproduzir usando o serviço de tua preferência), e implantar uma aplicação real desenvolvida com Ruby on Rails e PostgreSQL. Caso tu não tenha uma aplicação pra seguir o tutorial, pode usar essa aplicação MVP que desenvolvi e que é uma mini-rede social para publicação de customizações do jogo Animal Crossing: New Horizons, jogo do Nintendo Switch lançado em março de 2020. O código está disponível nesse repositório. Use o branch &lt;a href="https://github.com/stephannv/customdesigns/tree/tutorial" rel="noopener noreferrer"&gt;tutorial&lt;/a&gt; pois ele já está com alguns ajustes pra facilitar a configuração:&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/stephannv" rel="noopener noreferrer"&gt;
        stephannv
      &lt;/a&gt; / &lt;a href="https://github.com/stephannv/customdesigns" rel="noopener noreferrer"&gt;
        customdesigns
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      An application to share and discover Animal Crossing: New Horizons custom designs.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;README&lt;/h1&gt;

&lt;/div&gt;
&lt;p&gt;This README would normally document whatever steps are necessary to get the
application up and running.&lt;/p&gt;
&lt;p&gt;Things you may want to cover:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Ruby version&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;System dependencies&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Configuration&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Database creation&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Database initialization&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;How to run the test suite&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Services (job queues, cache servers, search engines, etc.)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Deployment instructions&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;...&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;



&lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/stephannv/customdesigns" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;h2&gt;
  
  
  Instalando o Dokku
&lt;/h2&gt;

&lt;p&gt;Antes de tudo é necessário criar uma conta na DigitalOcean, e criar uma instância, termo usado pela DO para se referir às instâncias é droplets, então crie um droplet. Não vai ter imagens desse processo nesse artigo pois a qualquer momento a DigitalOcean pode mudar toda a página e o artigo ficar desatualizado, mas não tem segredo, é só um formulário simples onde tu escolhe as configurações e finaliza o criação da instância. Uma máquina de 2GB de RAM é mais do que suficiente, mas já consegui configurar até usando a de 1GB de RAM.&lt;br&gt;
Após a instância criada, é necessário se conectar nela utilizando o usuário &lt;code&gt;root&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh root@IP_DO_TEU_SERVIDOR
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Esse &lt;code&gt;IP_DO_TEU_SERVIDOR&lt;/code&gt; se refere ao IP que a DigitalOcean forneceu após a criação do droplet. Caso já tenha configurado um domínio para essa instância, tu pode usar o domínio ao invés do IP.&lt;/p&gt;

&lt;p&gt;Agora instale o Dokku com o seguinte comando&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Para sistemas debian, instale o dokku via apt-get&lt;/span&gt;
wget https://raw.githubusercontent.com/dokku/dokku/v0.27.0/bootstrap.sh
&lt;span class="nb"&gt;sudo &lt;/span&gt;&lt;span class="nv"&gt;DOKKU_TAG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;v0.27.0 bash bootstrap.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A versão &lt;code&gt;0.27.0&lt;/code&gt; é a mais recente disponível enquanto eu escrevo esse artigo, então confira a documentação oficial no site para verificar se novas versões foram publicadas.&lt;/p&gt;

&lt;p&gt;Quando a instalação terminar, visite IP da tua máquina pelo navegador, preencha a chave pública do teu SSH no campo "Public SSH", pois isso te permitirá executar os comandos remotamente que vamos precisar mais pra frente. A sessão de configuração de hostname pode ser ignorada, vamos configurar isso já já. Finalize a instalação e tu estará pronto para começar a configuração da aplicação. &lt;/p&gt;

&lt;h2&gt;
  
  
  Configurando a aplicação
&lt;/h2&gt;

&lt;p&gt;Com o Dokku já instalado, é hora de criar a aplicação. Ainda conectado como root na tua instância, execute:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dokku apps:create meu_app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O &lt;code&gt;meu_app&lt;/code&gt; é o identificador da tua aplicação, substitua pelo nome do teu projeto, é ele que tu irá se referenciar no restante da configuração.&lt;/p&gt;

&lt;p&gt;Uma coisa legal do Dokku é que ele é compátivel com os buildpacks que são utilizados no Heroku, que são conjuntos de scripts que configuram automaticamente várias dependências da aplicação, sem precisar fazer esse processo de forma manual. Então adicione o buildpack do Ruby para seguirmos a configuração:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dokku buildpacks:add meu_app &lt;span class="se"&gt;\&lt;/span&gt;
  https://github.com/heroku/heroku-buildpack-ruby.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Para entender mais sobre os buildpacks: &lt;a href="https://devcenter.heroku.com/articles/buildpacks" rel="noopener noreferrer"&gt;https://devcenter.heroku.com/articles/buildpacks&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Caso esteja usando as credentials do Rails, tu precisa informar a master.key para que a aplicação possa descriptografar as credenciais:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dokku config:set meu_app &lt;span class="nv"&gt;RAILS_MASTER_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;tua_chave_mestra
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Também configure a variável &lt;code&gt;RAILS_ENV&lt;/code&gt; para ser production:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dokku config:set meu_app &lt;span class="nv"&gt;RAILS_ENV&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;production
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Com esse comando &lt;code&gt;dokku config:set&lt;/code&gt;, tu pode definir outras variáveis de ambiente também, caso sejam necessárias.&lt;/p&gt;

&lt;p&gt;Como o Heroku, o Dokku também permite que um projeto tenha um arquivo Procfile. Esse arquivo serve para definir os processos que serão executados na inicialização da aplicação. Dois deles são especiais, o &lt;code&gt;web&lt;/code&gt; e o &lt;code&gt;release&lt;/code&gt;. O &lt;code&gt;web&lt;/code&gt; é o único processo que pode receber as requisições externas roteadas pelo Dokku, e o &lt;code&gt;release&lt;/code&gt; é o comando executado antes de finalizar a implantação da aplicação.&lt;/p&gt;

&lt;p&gt;Tu pode ter outros processos e nomeá-los como quiser, por exemplo: depois de configurar um sidekiq na tua aplicação e no Dokku, será necessário colocar um &lt;code&gt;meus_workers: bundle exec sidekiq -C config/sidekiq.yml&lt;/code&gt; no &lt;code&gt;Procfile&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Para o escopo do artigo, precisa apenas do web para subir o servidor com o Puma e do release para executar as migrações e criar os seeds, então crie um arquivo Procfile na raíz do teu projeto com o seguinte conteúdo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;web: bundle &lt;span class="nb"&gt;exec &lt;/span&gt;puma &lt;span class="nt"&gt;-C&lt;/span&gt; config/puma.rb
release: bundle &lt;span class="nb"&gt;exec &lt;/span&gt;rails db:migrate db:seed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Para entender mais sobre o &lt;code&gt;Procfile&lt;/code&gt;: &lt;a href="https://devcenter.heroku.com/articles/procfile" rel="noopener noreferrer"&gt;https://devcenter.heroku.com/articles/procfile&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A partir do Rails 6, por questão de segurança, é necessário informar os domínios permitidos no header das requisições, então no &lt;code&gt;config/environments/production.rb&lt;/code&gt; informe o IP do teu servidor, teu domínio ou desabilite essa verificação. As opções são essas:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hosts&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s2"&gt;"IP_DO_TEU_SERVIDOR"&lt;/span&gt;
&lt;span class="c1"&gt;# ou&lt;/span&gt;
&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hosts&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s2"&gt;"meudominio.com"&lt;/span&gt;
&lt;span class="c1"&gt;# ou caso queira desabilitar a verificação&lt;/span&gt;
&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hosts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clear&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Envie as duas mudanças (do &lt;code&gt;Procfile&lt;/code&gt; e do &lt;code&gt;production.rb&lt;/code&gt;) para o teu repositório e vamos seguir para instalar o PostgreSQL.&lt;/p&gt;

&lt;h2&gt;
  
  
  Instalando o PostgreSQL
&lt;/h2&gt;

&lt;p&gt;Para instalar o PostgreSQL da maneira mais fácil, pode usar os plugins do Dokku:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dokku plugin:install &lt;span class="se"&gt;\&lt;/span&gt;
  https://github.com/dokku/dokku-postgres.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Após isso, crie o banco de dados que será usado na aplicação:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dokku postgres:create meu_app_db
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Observação: A versão do postgres instalada pode não ser a que você deseja, então você pode passar uma flag com uma versão específica: &lt;code&gt;dokku postgres:create meu_app_db -I 14.1&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Faça a conexão entre o banco de dados recém-criado e a aplicação com o seguinte comando:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dokku postgres:link meu_app_db meu_app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Esse comando configura uma variável de ambiente chamada &lt;code&gt;DATABASE_URL&lt;/code&gt; e ela será usada pela aplicação Rails para se conectar com o banco de dados. Uma informação extra: caso por algum motivo tu necessite que o banco seja acessado externamente, esse comando irá expor uma porta aleatória:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dokku postgres:expose meu_app_db
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Configurando o domínio
&lt;/h2&gt;

&lt;p&gt;Esse passo é opcional, caso tu tenha um domínio configurado apontando pro teu servidor e queira que ele se integre com a aplicação, tu precisa adicioná-lo ao meu_app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dokku domains:add meu_app meudominio.com

&lt;span class="c"&gt;# Caso queira o subdomínio www configurado:&lt;/span&gt;
dokku domains:add meu_app www.meudominio.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lembre-se da configuração que expliquei antes, sobre o &lt;code&gt;config.hosts&lt;/code&gt;, que é necessário adicionar o &lt;code&gt;meudominio.com&lt;/code&gt; no &lt;code&gt;config.hosts&lt;/code&gt; para conseguir navegar na aplicação utilizando o teu domínio.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fazendo o deploy
&lt;/h2&gt;

&lt;p&gt;Agora falta pouco para finalizar a implantação da aplicação. Na tua máquina local, na pasta do teu projeto, adicione o repositório remoto apontando para o Dokku do teu servidor com seguinte comando:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git remote add dokku dokku@IP_DO_TEU_SERVIDOR:meu_app
&lt;span class="c"&gt;# ou&lt;/span&gt;
git remote add dokku dokku@meudominio.com:meu_app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Para finalmente fazer o deploy agora ou toda vez que tu quiser mandar atualizações da aplicação, ainda na pasta do teu projeto execute:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git push dokku main &lt;span class="c"&gt;# ou qualquer outro branch que desejar colocar no ar&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Esse comando vai demorar um pouco, pois ele irá fazer todo o processo de instalar o Ruby de acordo com o buildpack adicionado lá no início do artigo, vai instalar as gems, instalar as dependências de front-end caso existam, precompilar os assets, rodar os comandos pré e pós deploy e por fim reiniciar o nginx.&lt;/p&gt;

&lt;p&gt;Agora tu pode acessar o &lt;code&gt;http://IP_DO_TEU_SERVIDOR&lt;/code&gt; ou &lt;code&gt;http://meudominio.com&lt;/code&gt; pelo navegador e se nada inesperado ocorrer, tua aplicação estará no ar, pronta para uso. Mas o ideal é que tua aplicação tenha SSL para dar mais segurança aos teus usuários, principalmente se tiver funcionalidade de autenticação ou se tratar dados mais sensíveis. Na próxima seção explico como configurar o SSL.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adicionando o SSL
&lt;/h2&gt;

&lt;p&gt;Caso tu já tenha configurado um domínio pra tua aplicação (caso contrário leia como fazer em Configurando o domínio) e queira configurar o SSL, primeiro instale o plugin do Let's Encrypt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dokku plugin:install &lt;span class="se"&gt;\&lt;/span&gt;
  https://github.com/dokku/dokku-letsencrypt.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Também é necessário configurar o teu e-mail para registro no Let's Encrypt, assim tu receberá notificações do teu certificado:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt; dokku config:set &lt;span class="nt"&gt;--no-restart&lt;/span&gt; meu_app &lt;span class="se"&gt;\&lt;/span&gt;
   &lt;span class="nv"&gt;DOKKU_LETSENCRYPT_EMAIL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;teu@email.aqui
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pra finalizar a configuração de fato o SSL, é só executar o comando letsencrypt e que ele fará todo o trabalho por baixo dos panos de gerar os certificados para cada um dos domínios adicionados:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dokku letsencrypt:enable meu_app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;E para ativar a renovação automática de 3 em 3 meses é com o seguinte comando:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dokku letsencrypt:cron-job &lt;span class="nt"&gt;--add&lt;/span&gt; meu_app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pronto, se tudo tiver dado certo, se tu acessar &lt;code&gt;https://meudominio.com&lt;/code&gt;, o navegador deverá mostrar na barra de navegação um cadeado sem nenhum erro ou alerta.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusão
&lt;/h2&gt;

&lt;p&gt;É isso, pode parecer complicado, mas é bem mais fácil que configurar uma máquina "crua" na mão. Com esse conceito de apps, fica fácil adicionar mais aplicações em um mesmo servidor, configurando um subdomínio para cada uma, até mesmo com tecnologias diferentes, pois o Dokku não é apenas para Ruby. É possível usar um Dockerfile ou uma imagem Docker no lugar dos buildpacks para ter uma configuração de ambiente mais customizada. Ele é uma ótima opção para MVPs ou aplicações com um baixo número de usuários, ou até para usar como ambiente de homologação (sandbox, QA).&lt;/p&gt;

&lt;p&gt;Caso tenha ficado interessado no Dokku, saiba que há outras alternativas de PaaS self-hosted para tu analisar os pontos fortes e fracos de cada um e escolher o que melhor se adequa para tuas necessidades. Numa pesquisa rápida consigo listar o Flynn, o CapRover, o Convox e o brasileiro Tsuru, mas com uma pesquisa mais aprofundada tu pode encontrar mais opções.&lt;/p&gt;

&lt;p&gt;Se tu já tiver usado o Dokku ou qualquer outra opção parecida, deixa nos comentários como foi a experiência, os pontos positivos e negativos, e alguma dica de configuração, caso tenha. Até o próximo artigo.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Entre na instância&lt;/span&gt;
&lt;span class="n"&gt;ssh&lt;/span&gt; &lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="vi"&gt;@IP_DO_TEU_SERVIDOR&lt;/span&gt;

&lt;span class="c1"&gt;# Instale o Dokku. Confira a última versão no site oficial.&lt;/span&gt;
&lt;span class="n"&gt;wget&lt;/span&gt; &lt;span class="n"&gt;https&lt;/span&gt;&lt;span class="ss"&gt;:/&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;githubusercontent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;dokku&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;dokku&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;v0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mf"&gt;27.0&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;bootstrap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sh&lt;/span&gt;
&lt;span class="no"&gt;DOKKU_TAG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;v0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mf"&gt;23.4&lt;/span&gt; &lt;span class="n"&gt;bash&lt;/span&gt; &lt;span class="n"&gt;bootstrap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sh&lt;/span&gt;

&lt;span class="c1"&gt;# Visite o IP_DO_TEU_SERVIDOR pelo navegador, adicione &lt;/span&gt;
&lt;span class="c1"&gt;# tua chave SSH, ignore as confgurações de hostname &lt;/span&gt;
&lt;span class="c1"&gt;# e finalize a instalação.&lt;/span&gt;

&lt;span class="c1"&gt;# Ainda conectado via SSH no servidor, crie um app no Dokku&lt;/span&gt;
&lt;span class="n"&gt;dokku&lt;/span&gt; &lt;span class="n"&gt;apps&lt;/span&gt;&lt;span class="ss"&gt;:create&lt;/span&gt; &lt;span class="n"&gt;meu_app&lt;/span&gt;

&lt;span class="c1"&gt;# Adicione o buildpack do Ruby&lt;/span&gt;
&lt;span class="n"&gt;dokku&lt;/span&gt; &lt;span class="n"&gt;buildpacks&lt;/span&gt;&lt;span class="ss"&gt;:add&lt;/span&gt; &lt;span class="n"&gt;meu_app&lt;/span&gt; &lt;span class="p"&gt;\&lt;/span&gt; 
  &lt;span class="n"&gt;https&lt;/span&gt;&lt;span class="ss"&gt;:/&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;github&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;heroku&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;heroku&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;buildpack&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;ruby&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;git&lt;/span&gt;

&lt;span class="c1"&gt;# Configure a variável RAILS_ENV para production&lt;/span&gt;
&lt;span class="n"&gt;dokku&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="ss"&gt;:set&lt;/span&gt; &lt;span class="n"&gt;meu_app&lt;/span&gt; &lt;span class="no"&gt;RAILS_ENV&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;production&lt;/span&gt;

&lt;span class="c1"&gt;# Caso use as credentials, informe a master.key&lt;/span&gt;
&lt;span class="n"&gt;dooku&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="ss"&gt;:set&lt;/span&gt; &lt;span class="n"&gt;meu_app&lt;/span&gt; &lt;span class="no"&gt;RAILS_MASTER_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;tua_senha&lt;/span&gt;

&lt;span class="c1"&gt;# Instale o PostgreSQL&lt;/span&gt;
&lt;span class="n"&gt;dokku&lt;/span&gt; &lt;span class="n"&gt;plugin&lt;/span&gt;&lt;span class="ss"&gt;:install&lt;/span&gt; &lt;span class="p"&gt;\&lt;/span&gt;
  &lt;span class="n"&gt;https&lt;/span&gt;&lt;span class="ss"&gt;:/&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;github&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;dokku&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;dokku&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;postgres&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;git&lt;/span&gt;

&lt;span class="c1"&gt;# Crie um banco de dados&lt;/span&gt;
&lt;span class="n"&gt;dokku&lt;/span&gt; &lt;span class="n"&gt;postgres&lt;/span&gt;&lt;span class="ss"&gt;:create&lt;/span&gt; &lt;span class="n"&gt;meu_app_db&lt;/span&gt;

&lt;span class="c1"&gt;# Conecte o banco de dados com tua aplicação&lt;/span&gt;
&lt;span class="n"&gt;dokku&lt;/span&gt; &lt;span class="n"&gt;postgres&lt;/span&gt;&lt;span class="ss"&gt;:link&lt;/span&gt; &lt;span class="n"&gt;meu_app_db&lt;/span&gt; &lt;span class="n"&gt;meu_app&lt;/span&gt;

&lt;span class="c1"&gt;# Caso tenha um domínio configurado no servidor:&lt;/span&gt;
&lt;span class="n"&gt;dokku&lt;/span&gt; &lt;span class="n"&gt;domains&lt;/span&gt;&lt;span class="ss"&gt;:add&lt;/span&gt; &lt;span class="n"&gt;meu_app&lt;/span&gt; &lt;span class="n"&gt;meudominio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;com&lt;/span&gt;

&lt;span class="c1"&gt;# Caso queira o subdomínio www configurado:&lt;/span&gt;
&lt;span class="n"&gt;dokku&lt;/span&gt; &lt;span class="n"&gt;domains&lt;/span&gt;&lt;span class="ss"&gt;:add&lt;/span&gt; &lt;span class="n"&gt;meu_app&lt;/span&gt; &lt;span class="n"&gt;www&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;meudominio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;com&lt;/span&gt;

&lt;span class="c1"&gt;# No código do teu projeto, lembre-se de informar&lt;/span&gt;
&lt;span class="c1"&gt;# o host que tua aplicação irá rodar.&lt;/span&gt;
&lt;span class="c1"&gt;# No arquivo /config/environments/production.rb&lt;/span&gt;
&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hosts&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s2"&gt;"IP_DO_TEU_SERVIDOR"&lt;/span&gt;

&lt;span class="c1"&gt;# Ou&lt;/span&gt;
&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hosts&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s2"&gt;"meudominio.com"&lt;/span&gt;

&lt;span class="c1"&gt;# Ou caso queira desabilitar a verificação&lt;/span&gt;
&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hosts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clear&lt;/span&gt;


&lt;span class="c1"&gt;# Na tua máquina local, aponte para o repositório&lt;/span&gt;
&lt;span class="c1"&gt;# remoto do Dokku do teu servidor:&lt;/span&gt;
&lt;span class="n"&gt;git&lt;/span&gt; &lt;span class="n"&gt;remote&lt;/span&gt; &lt;span class="n"&gt;add&lt;/span&gt; &lt;span class="n"&gt;dokku&lt;/span&gt; &lt;span class="n"&gt;dokku&lt;/span&gt;&lt;span class="vi"&gt;@IP_DO_TEU_SERVIDOR&lt;/span&gt;&lt;span class="ss"&gt;:meu_app&lt;/span&gt;

&lt;span class="c1"&gt;# Faça o deploy&lt;/span&gt;
&lt;span class="n"&gt;git&lt;/span&gt; &lt;span class="n"&gt;push&lt;/span&gt; &lt;span class="n"&gt;dokku&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt; 

&lt;span class="c1"&gt;# Acesse http://IP_DO_TEU_SERVIDOR ou&lt;/span&gt;
&lt;span class="c1"&gt;# http://meudominio.com e a aplicação estará no ar.&lt;/span&gt;

&lt;span class="c1"&gt;# Caso queira adicionar SSL na aplicação, instale &lt;/span&gt;
&lt;span class="c1"&gt;# o letsencrypt&lt;/span&gt;
&lt;span class="n"&gt;dokku&lt;/span&gt; &lt;span class="n"&gt;plugin&lt;/span&gt;&lt;span class="ss"&gt;:install&lt;/span&gt; &lt;span class="p"&gt;\&lt;/span&gt; 
  &lt;span class="n"&gt;https&lt;/span&gt;&lt;span class="ss"&gt;:/&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;github&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;dokku&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;dokku&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;letsencrypt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;git&lt;/span&gt;

&lt;span class="c1"&gt;# Configure o e-mail para receber alertas do&lt;/span&gt;
&lt;span class="c1"&gt;# Let's Encrypt:&lt;/span&gt;
&lt;span class="n"&gt;dokku&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="ss"&gt;:set&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;no&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;restart&lt;/span&gt; &lt;span class="n"&gt;meu_app&lt;/span&gt; &lt;span class="p"&gt;\&lt;/span&gt;
  &lt;span class="no"&gt;DOKKU_LETSENCRYPT_EMAIL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;teu_email&lt;/span&gt;

&lt;span class="c1"&gt;# Crie os certificados para os domínios da aplicação&lt;/span&gt;
&lt;span class="n"&gt;dokku&lt;/span&gt; &lt;span class="n"&gt;letsencrypt&lt;/span&gt; &lt;span class="n"&gt;meu_app&lt;/span&gt;

&lt;span class="c1"&gt;# Configure a renovação automática&lt;/span&gt;
&lt;span class="n"&gt;dokku&lt;/span&gt; &lt;span class="n"&gt;letsencrypt&lt;/span&gt;&lt;span class="ss"&gt;:cron&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;job&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt; &lt;span class="n"&gt;meu_app&lt;/span&gt;

&lt;span class="c1"&gt;# Acesse https://IP_DO_TEU_SERVIDOR ou&lt;/span&gt;
&lt;span class="c1"&gt;# https://meudominio.com e a aplicação estará no ar&lt;/span&gt;
&lt;span class="c1"&gt;# com SSL&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>rails</category>
      <category>ruby</category>
      <category>dokku</category>
      <category>postgres</category>
    </item>
    <item>
      <title>Utilizando o padrão interactor no Ruby on Rails</title>
      <dc:creator>Stephann</dc:creator>
      <pubDate>Sun, 20 Mar 2022 23:36:54 +0000</pubDate>
      <link>https://dev.to/stephann/utilizando-o-padrao-interactor-no-ruby-on-rails-iom</link>
      <guid>https://dev.to/stephann/utilizando-o-padrao-interactor-no-ruby-on-rails-iom</guid>
      <description>&lt;h2&gt;
  
  
  Introdução
&lt;/h2&gt;

&lt;p&gt;Um problema que é comum e que todo desenvolvedor Rails já se deparou, é ver a qualidade da base de código se deteriorar conforme as regras de negócio vão aumentando e ficando mais complexas. Numa equipe ainda sem muita experiência, é quase certo que vão aparecer na aplicação os famosos fat controllers. Os controllers gordos são comuns e eles nada mais são que aqueles controllers cheios de responsabilidades e comportamentos que deveriam estar em outras camadas. Depois que o desenvolvedor aprende na prática e entende que fat controllers são prejudiciais, ele tende a começar a escrever quase toda lógica de negócio nos modelos, e que o leva aos fat models. O problema dos modelos gordos é o mesmo, é uma classe com vários comportamentos centralizados e que deveriam estar distribuídos de uma melhor forma. Com a ideia de ajudar a fugir dos modelos e controllers mais gordinhos, os concerns foram introduzidos no Rails. Eles são módulos que permitem definir comportamentos em um arquivo separado e que podem ser incluídos em outras classes. Mas enquanto os concerns ajudam a escrever modelos e controllers menores em quantidade de linhas, não resolvem o problema deles estarem estarem inchados de comportamentos.&lt;/p&gt;

&lt;p&gt;Aí o desenvolvedor se questiona: "Então se fat controllers são ruins e fat models também, onde vou colocar meu código? Na camada de view?  Ou devo abandonar a profissão de desenvolvedor e vender minha arte na praia?". Nenhuma das duas opções. Há alguns padrões de design que ajudam a organizar seu código sem entupir uma classe de métodos e responsabilidades, tipo os Form Objects, Service Objects, Query Objects, Clients, Interactors e etc. Nesse artigo vou apresentar alguns conceitos dos interactors, mostrar algumas gems que já testei e exemplificar o uso do padrão com situações quase reais.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conceito
&lt;/h2&gt;

&lt;p&gt;Primeiro quero deixar claro que não há um consenso exato na comunidade sobre o que é um interactor. Você vai encontrar uma galera na internet usando os termos service objects, operations, use-cases, mutations, commands ao se referir a um mesmo conceito, já outro desenvolvedor saberá dizer a diferença entre cada um, a outra irá discordar e explicará de outra maneira, mas de forma geral, estarão falando sobre as mesmas coisas ou pelo menos sobre soluções bem parecidas. O fato é que um interactor nada mais é do que um objeto simples, com um propósito único, que encapsula sua regra de negócio e representa uma funcionalidade da sua aplicação. Explicando com um exemplo: Sua aplicação é uma newsletter e você deve enviar um e-mail para todos os assinantes toda vez que um artigo novo for criado e ele não estiver marcado como rascunho. Uma das soluções é fazer isso no controller:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/controllers/articles_controllers.rb &lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ArticlesController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt; 
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt; 
    &lt;span class="vi"&gt;@article&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;post_params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt; 
      &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="vi"&gt;@article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;draft?&lt;/span&gt; 
        &lt;span class="no"&gt;Member&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;subscribed: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; 
          &lt;span class="no"&gt;SendMailJob&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perform_async&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="vi"&gt;@article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
        &lt;span class="k"&gt;end&lt;/span&gt; 
      &lt;span class="k"&gt;end&lt;/span&gt; 

      &lt;span class="n"&gt;redirect_to&lt;/span&gt; &lt;span class="vi"&gt;@article&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;notice: &lt;/span&gt;&lt;span class="s1"&gt;'Success!'&lt;/span&gt; 
    &lt;span class="k"&gt;else&lt;/span&gt; 
      &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;:new&lt;/span&gt; 
    &lt;span class="k"&gt;end&lt;/span&gt; 
  &lt;span class="k"&gt;end&lt;/span&gt; 
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;A outra opção é tirar isso do controller e jogar a lógica para o modelo:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/models/article.rb &lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Article&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt; 
  &lt;span class="n"&gt;after_create&lt;/span&gt; &lt;span class="ss"&gt;:send_mail_to_members&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;unless: :draft?&lt;/span&gt; 

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;send_mail_to_members&lt;/span&gt; 
    &lt;span class="no"&gt;Member&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;subscribed: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; 
      &lt;span class="no"&gt;SendMailJob&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perform_async&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
    &lt;span class="k"&gt;end&lt;/span&gt; 
  &lt;span class="k"&gt;end&lt;/span&gt; 
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Se tua intenção é evitar código acoplado e a duplicação de código, as duas soluções acima não te ajudam nesses pontos. Além de atribuir responsabilidades que fogem do escopo dessas classes, colocar o comportamento no controller torna mais difícil a reutilização em outros locais da aplicação e colocar num callback do ActiveRecord, você enterra esse comportamento num funcionamento interno da classe, dificultando a manutenção, a implementação de testes e causando surpresas para quem for usar aquela classe um dia e descobrir que acabou enviando alguns milhares de e-mails só porque salvou um artigo na tabela.&lt;/p&gt;

&lt;p&gt;Para ajudar resolver esses problemas, o interactor entra em cena e essa situação seria resolvida da seguinte forma:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Uma observação antes de mostrar o código: Vou usar nos exemplos a gem &lt;a href="https://github.com/collectiveidea/interactor"&gt;interactor&lt;/a&gt;, mas mais adiante no texto apresento outras opções, inclusive uma que eu estou utilizado nos projetos e estou gostando bastante.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/interactors/create_article.rb &lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CreateArticle&lt;/span&gt; 
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Interactor&lt;/span&gt; 

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt; 
    &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;article&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;
      &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;draft?&lt;/span&gt; 
        &lt;span class="no"&gt;Member&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;subscribed: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; 
          &lt;span class="no"&gt;SendMailJob&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perform_async&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
        &lt;span class="k"&gt;end&lt;/span&gt; 
      &lt;span class="k"&gt;end&lt;/span&gt; 
    &lt;span class="k"&gt;else&lt;/span&gt; 
      &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fail!&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;valid?&lt;/span&gt; 
    &lt;span class="k"&gt;end&lt;/span&gt; 
  &lt;span class="k"&gt;end&lt;/span&gt; 
&lt;span class="k"&gt;end&lt;/span&gt; 

&lt;span class="c1"&gt;# app/controllers/articles_controller.rb &lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ArticlesController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt; 
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt; 
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;CreateArticle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;params: &lt;/span&gt;&lt;span class="n"&gt;article_params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;success?&lt;/span&gt; 
      &lt;span class="n"&gt;redirect_to&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;article&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;notice: &lt;/span&gt;&lt;span class="s1"&gt;'Success!'&lt;/span&gt; 
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="vi"&gt;@article&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;article&lt;/span&gt; 
      &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;:new&lt;/span&gt; 
    &lt;span class="k"&gt;end&lt;/span&gt; 
  &lt;span class="k"&gt;end&lt;/span&gt; 
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Dessa forma o controller não precisa mais saber sobre a regra de negócio, o papel dele é receber a requisição, delegar o processamento dos dados e depois cuidar de devolver uma resposta. E você também não sujou o modelo com callbacks.&lt;/p&gt;

&lt;p&gt;Vale a pena notar que diferente do que aprendemos nas cadeiras Orientação a Objeto, a classe &lt;code&gt;CreateArticle&lt;/code&gt; não representa a abstração de um objeto com propriedades, métodos, com herança, polimorfismo e etc, mas sim uma ação, como o próprio nome já representa: &lt;code&gt;CriarArtigo&lt;/code&gt;. Quase como que voltando para a programação estrutural mas em uma escala de caso de uso. O mesmo esquema poderia ser utilizado para outras coisas como &lt;code&gt;FinalizarPedido&lt;/code&gt;, &lt;code&gt;ProcessarWebhook&lt;/code&gt;, &lt;code&gt;ImportarArquivos&lt;/code&gt; e etc. Onde toda a lógica de verificar estoque pra confirmar pedido, verificar a assinatura do webhook, ler, transformar e importar dados de arquivos ficariam em interactors, e não mais em modelos ou controllers.&lt;/p&gt;
&lt;h2&gt;
  
  
  Organizando os interactors
&lt;/h2&gt;

&lt;p&gt;Não é porque agora você colocou interactors na aplicação que tudo vai ficar mil maravilhas e nada de ruim vai acontecer. Também corre o risco de ter interactors gigantes, com códigos repetidos, fazendo mais coisas do que deveriam. Por isso há um conceito de orquestradores (também chamados de organizadores) no padrão interactor, que são basicamente interactors que executam outros interactors. Vou trazer um novo exemplo pra ficar claro os benefícios desses orquestradores: Minha aplicação precisa importar e processar os arquivos de retorno bancário da FEBRABAN para saber se os boletos que minha aplicação emitiu foram pagos ou não. O passo a passo desse processo é o seguinte: Pegar os arquivos no FTP, transformar os dados arquivos em objetos ruby, e processar esses arquivos atualizando os boletos e pedidos do sistema. Utilizando apenas um interactor seria mais ou menos assim:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/interactors/import_bank_files.rb &lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ImportBankFiles&lt;/span&gt; 
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Interactor&lt;/span&gt; 

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt; 
    &lt;span class="c1"&gt;# abre conexão com FTP &lt;/span&gt;
    &lt;span class="n"&gt;ftp_client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; 
    &lt;span class="n"&gt;ftp_connection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ftp_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 

    &lt;span class="c1"&gt;# pega arquivos &lt;/span&gt;
    &lt;span class="n"&gt;bank_files&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ftp_connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ls&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;

    &lt;span class="c1"&gt;# lê arquivos txt e transforma em objetos ruby &lt;/span&gt;
    &lt;span class="n"&gt;parsed_bank_files&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bank_files&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; 

    &lt;span class="c1"&gt;# processa arquivos &lt;/span&gt;
    &lt;span class="n"&gt;parsed_bank_files&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;bank_file&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; 
      &lt;span class="n"&gt;boleto&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Boleto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;identifier: &lt;/span&gt;&lt;span class="n"&gt;bank_file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;identifier&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 

      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;boleto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;nil?&lt;/span&gt; 
        &lt;span class="no"&gt;Sentry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;capture_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"not found: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;bank_file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;identifier&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
        &lt;span class="k"&gt;next&lt;/span&gt; 
      &lt;span class="k"&gt;else&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;bank_file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'09'&lt;/span&gt; &lt;span class="c1"&gt;# SETTLE BOLETO &lt;/span&gt;
          &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;boleto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;settled?&lt;/span&gt; 
            &lt;span class="n"&gt;boleto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;paid_at: &lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
            &lt;span class="n"&gt;boleto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;status: &lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
            &lt;span class="no"&gt;PaymentReceivedMailer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;boleto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;payer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
          &lt;span class="k"&gt;end&lt;/span&gt; 
        &lt;span class="k"&gt;elsif&lt;/span&gt; &lt;span class="n"&gt;bank_file_code&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; 
          &lt;span class="o"&gt;...&lt;/span&gt; 
        &lt;span class="k"&gt;end&lt;/span&gt; 
      &lt;span class="k"&gt;end&lt;/span&gt; 
    &lt;span class="k"&gt;end&lt;/span&gt; 
  &lt;span class="k"&gt;end&lt;/span&gt; 
&lt;span class="k"&gt;end&lt;/span&gt; 

&lt;span class="c1"&gt;# Em algum outro lugar para executar a importação &lt;/span&gt;
&lt;span class="no"&gt;ImportBankFiles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Abstraí bem os detalhes da implementação porque não é a intenção mostrar como processar arquivos de retorno bancário, e sim exemplificar um processo mais complexo. Esse código poderia estar pior e estaria se estivesse, por exemplo, num controller ou num modelo, mas temos como melhorar dividindo as responsabilidades em interactors menores e chamando tudo junto com um orquestrador. Primeiro destrinchando o comportamento em múltiplos interactors:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/interactors/fetch_bank_files_from_ftp.rb &lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;FetchBankFilesFromFTP&lt;/span&gt; 
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Interactor&lt;/span&gt; 

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt; 
    &lt;span class="c1"&gt;# abre conexão com FTP &lt;/span&gt;
    &lt;span class="n"&gt;ftp_client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; 
    &lt;span class="n"&gt;ftp_connection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ftp_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 

    &lt;span class="c1"&gt;# pega arquivos e coloca no contexto &lt;/span&gt;
    &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bank_files&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ftp_connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ls&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt; 
  &lt;span class="k"&gt;end&lt;/span&gt; 
&lt;span class="k"&gt;end&lt;/span&gt; 

&lt;span class="c1"&gt;# app/interactors/parse_bank_files.rb &lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ParseBankFiles&lt;/span&gt; 
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Interactor&lt;/span&gt; 

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt; 
    &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parsed_bank_files&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bank_files&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;bf&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; 
      &lt;span class="c1"&gt;# transforma os arquivos txt em objetos ruby&lt;/span&gt;
      &lt;span class="o"&gt;...&lt;/span&gt; 
    &lt;span class="k"&gt;end&lt;/span&gt; 
  &lt;span class="k"&gt;end&lt;/span&gt; 
&lt;span class="k"&gt;end&lt;/span&gt; 

&lt;span class="c1"&gt;# app/interactors/import_parsed_bank_files.rb &lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ImportParsedBankFiles&lt;/span&gt; 
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Interactor&lt;/span&gt; 

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt; 
    &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parsed_bank_files&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;parsed_bank_file&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; 
      &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="c1"&gt;# quita os boletos, atualiza os pedidos e etc. &lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt; 
  &lt;span class="k"&gt;end&lt;/span&gt; 
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Atenção nos dados sendo compartilhados via contexto para outros interactors poderem acessá-los. Agora junta tudo com um orquestrador:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/organizers/import_bank_files.rb &lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ImportBankFiles&lt;/span&gt; 
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Interactor&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Organizer&lt;/span&gt; 

  &lt;span class="n"&gt;organize&lt;/span&gt; &lt;span class="no"&gt;FetchBankFilesFromFTP&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="no"&gt;ParseBankFiles&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="no"&gt;ImportParsedBankFiles&lt;/span&gt; 
  &lt;span class="k"&gt;end&lt;/span&gt; 
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# Usando o orquestrador em algum lugar da aplicação &lt;/span&gt;
&lt;span class="no"&gt;ImportBankfiles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Dividir os interactors dessa forma traz algumas vantagens: Deixa o código mais manutenível, facilita a implementação dos testes e ainda torna o código reutilizável. Vamos supor que agora a aplicação precisa permitir o recebimento de arquivos bancários por um formulário para caso o FTP esteja com problema. Você criaria mais um orquestrador chamado de &lt;code&gt;UploadBankFiles&lt;/code&gt;, e você conseguiria reutilizar pelo menos o &lt;code&gt;ParseBankFiles&lt;/code&gt; e o &lt;code&gt;ImportParsedBankFiles&lt;/code&gt; no fluxo de recebimento desses arquivos.&lt;/p&gt;
&lt;h2&gt;
  
  
  Gems disponíveis
&lt;/h2&gt;

&lt;p&gt;Implementar o padrão interactor não é tão complicado. É basicamente um PORO com um método público chamado &lt;code&gt;#call&lt;/code&gt; (ou &lt;code&gt;#run&lt;/code&gt;, ou &lt;code&gt;#execute&lt;/code&gt; ou qualquer coisa que você preferir). Depois você pode ir incrementando sua própria implementação do padrão, adicionando mensagens, tratamento de erros, contextos, rollbacks, orquestradores e assim por diante. Mas o que não falta são gems pra você adicionar no projeto e já começar a escrever interactors agora. Vou colocar a seguir uma lista com as gems que eu pelo menos li a documentação, e as que eu já usei em algum projeto eu vou deixar alguns breves comentários.&lt;/p&gt;
&lt;h3&gt;
  
  
  collectiveidea/interactor:
&lt;/h3&gt;

&lt;p&gt;Essa foi a primeira que usei. Gosto dela pela simplicidade e tem uma boa API permitindo mais flexibidade mas não fornece uma opção de declarar os parâmetros de entrada e saída de um interactor.&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--A9-wwsHG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/collectiveidea"&gt;
        collectiveidea
      &lt;/a&gt; / &lt;a href="https://github.com/collectiveidea/interactor"&gt;
        interactor
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Interactor provides a common interface for performing complex user interactions.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;h1&gt;
Interactor&lt;/h1&gt;
&lt;p&gt;&lt;a href="http://rubygems.org/gems/interactor" rel="nofollow"&gt;&lt;img src="https://camo.githubusercontent.com/8fff526e43633a090984bcc17ea5cecaead7b96624a1e44f38b3cc70430fb382/68747470733a2f2f696d672e736869656c64732e696f2f67656d2f762f696e7465726163746f722e737667" alt="Gem Version"&gt;&lt;/a&gt;
&lt;a href="https://github.com/collectiveidea/interactor/actions/workflows/tests.yml"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--5eumZamW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://github.com/collectiveidea/interactor/actions/workflows/tests.yml/badge.svg" alt="Build Status"&gt;&lt;/a&gt;
&lt;a href="https://codeclimate.com/github/collectiveidea/interactor" rel="nofollow"&gt;&lt;img src="https://camo.githubusercontent.com/48a429cd599131ee2b20c53b2a3d6f7a3a42feb45233dc31b7f27efe296d5c86/68747470733a2f2f696d672e736869656c64732e696f2f636f6465636c696d6174652f6d61696e7461696e6162696c6974792f636f6c6c656374697665696465612f696e7465726163746f722e737667" alt="Maintainability"&gt;&lt;/a&gt;
&lt;a href="https://codeclimate.com/github/collectiveidea/interactor" rel="nofollow"&gt;&lt;img src="https://camo.githubusercontent.com/ac8e36430e2f1493b4ae425ce0a467846e440e7d55d9d55193c3dd08bfc80c50/68747470733a2f2f696d672e736869656c64732e696f2f636f6465636c696d6174652f636f7665726167652d6c65747465722f636f6c6c656374697665696465612f696e7465726163746f722e737667" alt="Test Coverage"&gt;&lt;/a&gt;
&lt;a href="https://github.com/testdouble/standard"&gt;&lt;img src="https://camo.githubusercontent.com/bde227e3207c7143032c0feb73889ffbda8eb1ef234b820b915ccaf74f9c66d7/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f636f64655f7374796c652d7374616e646172642d627269676874677265656e2e737667" alt="Ruby Style Guide"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
Getting Started&lt;/h2&gt;
&lt;p&gt;Add Interactor to your Gemfile and &lt;code&gt;bundle install&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight highlight-source-ruby notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-en"&gt;gem&lt;/span&gt; &lt;span class="pl-s"&gt;"interactor"&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt; &lt;span class="pl-s"&gt;"~&amp;gt; 3.0"&lt;/span&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
What is an Interactor?&lt;/h2&gt;
&lt;p&gt;An interactor is a simple, single-purpose object.&lt;/p&gt;
&lt;p&gt;Interactors are used to encapsulate your application's
&lt;a href="http://en.wikipedia.org/wiki/Business_logic" rel="nofollow"&gt;business logic&lt;/a&gt;. Each interactor
represents one thing that your application &lt;em&gt;does&lt;/em&gt;.&lt;/p&gt;
&lt;h3&gt;
Context&lt;/h3&gt;
&lt;p&gt;An interactor is given a &lt;em&gt;context&lt;/em&gt;. The context contains everything the
interactor needs to do its work.&lt;/p&gt;
&lt;p&gt;When an interactor does its single purpose, it affects its given context.&lt;/p&gt;
&lt;h4&gt;
Adding to the Context&lt;/h4&gt;
&lt;p&gt;As an interactor runs it can add information to the context.&lt;/p&gt;
&lt;div class="highlight highlight-source-ruby notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-en"&gt;context&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;user&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-en"&gt;user&lt;/span&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h4&gt;
Failing the Context&lt;/h4&gt;
&lt;p&gt;When something goes wrong in your interactor, you can flag the context as
failed.&lt;/p&gt;
&lt;div class="highlight highlight-source-ruby notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-en"&gt;context&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;fail!&lt;/span&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;When given a hash argument, the &lt;code&gt;fail!&lt;/code&gt; method can also update the context. The
following are equivalent:&lt;/p&gt;
&lt;div class="highlight highlight-source-ruby notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-en"&gt;context&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;error&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-s"&gt;"Boom!"&lt;/span&gt;
&lt;span class="pl-en"&gt;context&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;fail!&lt;/span&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="highlight highlight-source-ruby notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-en"&gt;context&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;fail!&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-pds"&gt;error&lt;/span&gt;: &lt;/pre&gt;…
&lt;/div&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/collectiveidea/interactor"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;



&lt;h3&gt;
  
  
  adomokos/light-service:
&lt;/h3&gt;

&lt;p&gt;O diferencial da light-service é que ela tem uma API mais avançada para os orquestradores, mais opções para o controle de fluxo e também permite uma declarar os parâmetros de entrada e os resultados gerados pelo interactor.&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--A9-wwsHG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/adomokos"&gt;
        adomokos
      &lt;/a&gt; / &lt;a href="https://github.com/adomokos/light-service"&gt;
        light-service
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Series of Actions with an emphasis on simplicity.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer nofollow" href="https://raw.githubusercontent.com/adomokos/light-service/master/resources/light-service.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--0iyT8Df3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/adomokos/light-service/master/resources/light-service.png" alt="LightService"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://rubygems.org/gems/light-service" rel="nofollow"&gt;&lt;img src="https://camo.githubusercontent.com/20ad04516011ba191a72a7efa157d699b50459e66be69264a14c60fcf00339fc/68747470733a2f2f696d672e736869656c64732e696f2f67656d2f762f6c696768742d736572766963652e737667" alt="Gem Version"&gt;&lt;/a&gt;
&lt;a href="https://github.com/adomokos/light-service/actions/workflows/project-build.yml"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--EtGam7x3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://github.com/adomokos/light-service/actions/workflows/project-build.yml/badge.svg" alt="CI Tests"&gt;&lt;/a&gt;
&lt;a href="https://codecov.io/gh/adomokos/light-service" rel="nofollow"&gt;&lt;img src="https://camo.githubusercontent.com/0e8901eca8e81f2d5196f397a6538e0832d8ec7640e23df44ba4a5b94d11b96b/68747470733a2f2f636f6465636f762e696f2f67682f61646f6d6f6b6f732f6c696768742d736572766963652f6272616e63682f6d61737465722f67726170682f62616467652e737667" alt="codecov"&gt;&lt;/a&gt;
&lt;a href="https://codeclimate.com/github/adomokos/light-service" rel="nofollow"&gt;&lt;img src="https://camo.githubusercontent.com/f21367d4841f9ceb2b3aa25abb60ed4752652b6c04f72538dd2b01133658abeb/68747470733a2f2f636f6465636c696d6174652e636f6d2f6769746875622f61646f6d6f6b6f732f6c696768742d736572766963652e737667" alt="Code Climate"&gt;&lt;/a&gt;
&lt;a href="http://opensource.org/licenses/MIT" rel="nofollow"&gt;&lt;img src="https://camo.githubusercontent.com/45b4ffbd594af47fe09a3432f9f8e122c6518aa6352b4ce453a1a2563da2905c/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d677265656e2e737667" alt="License"&gt;&lt;/a&gt;
&lt;a href="https://rubygems.org/gems/light-service" rel="nofollow"&gt;&lt;img src="https://camo.githubusercontent.com/545fb46f3bbeb3199a233928fc31e9da057081f914ea54cd375f9feecd307796/68747470733a2f2f727562792d67656d2d646f776e6c6f6164732d62616467652e6865726f6b756170702e636f6d2f6c696768742d736572766963653f747970653d746f74616c" alt="Download Count"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;LightService is a powerful and flexible service skeleton framework with an emphasis on simplicity&lt;/p&gt;
&lt;h2&gt;
Table of Contents&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/adomokos/light-service#table-of-contents"&gt;Table of Contents&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/adomokos/light-service#why-lightservice"&gt;Why LightService?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/adomokos/light-service#getting-started"&gt;Getting started&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/adomokos/light-service#requirements"&gt;Requirements&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/adomokos/light-service#installation"&gt;Installation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/adomokos/light-service#your-first-action"&gt;Your first action&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/adomokos/light-service#your-first-organizer"&gt;Your first organizer&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/adomokos/light-service#stopping-the-series-of-actions"&gt;Stopping the Series of Actions&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/adomokos/light-service#failing-the-context"&gt;Failing the Context&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/adomokos/light-service#skipping-the-rest-of-the-actions"&gt;Skipping the rest of the actions&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/adomokos/light-service#benchmarking-actions-with-around-advice"&gt;Benchmarking Actions with Around Advice&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/adomokos/light-service#before-and-after-action-hooks"&gt;Before and After Action Hooks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/adomokos/light-service#expects-and-promises"&gt;Expects and Promises&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/adomokos/light-service#default-values-for-optional-expected-keys"&gt;Default values for optional Expected keys&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/adomokos/light-service#key-aliases"&gt;Key Aliases&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/adomokos/light-service#logging"&gt;Logging&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/adomokos/light-service#error-codes"&gt;Error Codes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/adomokos/light-service#action-rollback"&gt;Action Rollback&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/adomokos/light-service#localizing-messages"&gt;Localizing Messages&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/adomokos/light-service#built-in-localization-adapter"&gt;Built-in localization adapter&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/adomokos/light-service#i18n-localization-adapter"&gt;I18n localization adapter&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/adomokos/light-service#custom-localization-adapter"&gt;Custom localization adapter&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/adomokos/light-service#orchestrating-logic-in-organizers"&gt;Orchestrating Logic in Organizers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/adomokos/light-service#contextfactory-for-faster-action-testing"&gt;ContextFactory for Faster Action Testing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/adomokos/light-service#rails-support"&gt;Rails support&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/adomokos/light-service#organizer-generation"&gt;Organizer generation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/adomokos/light-service#action-generation"&gt;Action generation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/adomokos/light-service#advanced-action-generation"&gt;Advanced action generation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/adomokos/light-service#other-implementations"&gt;Other implementations&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/adomokos/light-service#contributing"&gt;Contributing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/adomokos/light-service#release-notes"&gt;Release Notes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/adomokos/light-service#license"&gt;License&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
Why LightService?&lt;/h2&gt;
&lt;p&gt;What do you think of this code?&lt;/p&gt;
&lt;div class="highlight highlight-source-ruby notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-k"&gt;class&lt;/span&gt; &lt;span class="pl-v"&gt;TaxController&lt;/span&gt; &amp;lt; &lt;span class="pl-v"&gt;ApplicationController&lt;/span&gt;
  &lt;span class="pl-k"&gt;def&lt;/span&gt; &lt;span class="pl-en"&gt;update&lt;/span&gt;
    &lt;span class="pl-c1"&gt;@order&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-v"&gt;Order&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;find&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-en"&gt;params&lt;/span&gt;&lt;span class="pl-kos"&gt;[&lt;/span&gt;&lt;span class="pl-pds"&gt;:id&lt;/span&gt;&lt;span class="pl-kos"&gt;]&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;
    &lt;span class="pl-s1"&gt;tax_ranges&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-v"&gt;TaxRange&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;for_region&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-en"&gt;order&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;region&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;
    &lt;span class="pl-k"&gt;if&lt;/span&gt; &lt;span class="pl-s1"&gt;tax_ranges&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;nil?&lt;/span&gt;
      &lt;span class="pl-en"&gt;render&lt;/span&gt; &lt;span class="pl-pds"&gt;:action&lt;/span&gt; &lt;span class="pl-c1"&gt;=&amp;gt;&lt;/span&gt;&lt;/pre&gt;…
&lt;/div&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/adomokos/light-service"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h3&gt;
  
  
  sunny/actor:
&lt;/h3&gt;

&lt;p&gt;Essa é a minha preferida. Estou usando ela em todos os meus projetos. Ela não tem um orquestrador tão versátil quanto os da light-service, mas o que tem lá me serve bem, e a definição de parâmetros ajuda bastante pois tem como definir algumas opções, como tipos e valores padrões.&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--A9-wwsHG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/sunny"&gt;
        sunny
      &lt;/a&gt; / &lt;a href="https://github.com/sunny/actor"&gt;
        actor
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Composable Ruby service objects
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;h1&gt;
ServiceActor&lt;/h1&gt;
&lt;p&gt;This Ruby gem lets you move your application logic into into small composable
service objects. It is a lightweight framework that helps you keep your models
and controllers thin.&lt;/p&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer nofollow" href="https://user-images.githubusercontent.com/132/78340166-e7567000-7595-11ea-97c0-b3e5da2de7a1.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--E7l0ljXC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://user-images.githubusercontent.com/132/78340166-e7567000-7595-11ea-97c0-b3e5da2de7a1.png" alt="Photo of theater seats"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
Contents&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/sunny/actor#installation"&gt;Installation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/sunny/actor#usage"&gt;Usage&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/sunny/actor#inputs"&gt;Inputs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/sunny/actor#outputs"&gt;Outputs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/sunny/actor#fail"&gt;Fail&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/sunny/actor#play-actors-in-a-sequence"&gt;Play actors in a sequence&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/sunny/actor#rollback"&gt;Rollback&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/sunny/actor#inline-actors"&gt;Inline actors&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/sunny/actor#play-conditions"&gt;Play conditions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/sunny/actor#input-aliases"&gt;Input aliases&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/sunny/actor#input-options"&gt;Input options&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/sunny/actor#defaults"&gt;Defaults&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/sunny/actor#allow-nil"&gt;Allow nil&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/sunny/actor#conditions"&gt;Conditions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/sunny/actor#types"&gt;Types&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/sunny/actor#custom-input-errors"&gt;Custom input errors&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/sunny/actor#testing"&gt;Testing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/sunny/actor#faq"&gt;FAQ&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/sunny/actor#thanks"&gt;Thanks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/sunny/actor#contributing"&gt;Contributing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/sunny/actor#contributing"&gt;License&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
Installation&lt;/h2&gt;
&lt;p&gt;Add the gem to your application’s Gemfile by executing:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;bundle add service_actor&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
Extensions&lt;/h3&gt;
&lt;p&gt;For &lt;strong&gt;Rails generators&lt;/strong&gt;, you can use the
&lt;a href="https://github.com/sunny/actor-rails"&gt;service_actor-rails&lt;/a&gt; gem:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;bundle add service_actor-rails&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;For &lt;strong&gt;TTY prompts&lt;/strong&gt;, you can use the
&lt;a href="https://github.com/pboling/service_actor-promptable"&gt;service_actor-promptable&lt;/a&gt; gem:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;bundle add service_actor-promptable&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
Usage&lt;/h2&gt;
&lt;p&gt;Actors are single-purpose actions in your application that represent your
business logic. They start with a verb, inherit from &lt;code&gt;Actor&lt;/code&gt; and implement a
&lt;code&gt;call&lt;/code&gt; method.&lt;/p&gt;
&lt;div class="highlight highlight-source-ruby notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-c"&gt;# app/actors/send_notification.rb&lt;/span&gt;
&lt;span class="pl-k"&gt;class&lt;/span&gt; &lt;span class="pl-v"&gt;SendNotification&lt;/span&gt; &amp;lt; &lt;span class="pl-v"&gt;Actor&lt;/span&gt;
  &lt;span class="pl-k"&gt;def&lt;/span&gt; &lt;span class="pl-en"&gt;call&lt;/span&gt;
    &lt;span class="pl-c"&gt;# …&lt;/span&gt;
  &lt;span class="pl-k"&gt;end&lt;/span&gt;
&lt;span class="pl-k"&gt;end&lt;/span&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Trigger them in your application with &lt;code&gt;.call&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight highlight-source-ruby notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-v"&gt;SendNotification&lt;/span&gt;&lt;/pre&gt;…
&lt;/div&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/sunny/actor"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h3&gt;
  
  
  cypriss/mutations:
&lt;/h3&gt;

&lt;p&gt;Usei essa em alguns projetos, mas a falta de orquestradores, a dificuldade de encadear chamadas e a definição de parâmetros de entrada e a burocracia de uso deles me afastaram.&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--A9-wwsHG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/cypriss"&gt;
        cypriss
      &lt;/a&gt; / &lt;a href="https://github.com/cypriss/mutations"&gt;
        mutations
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Compose your business logic into commands that sanitize and validate input.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;h1&gt;
Mutations&lt;/h1&gt;
&lt;p&gt;&lt;a href="https://travis-ci.org/cypriss/mutations" rel="nofollow"&gt;&lt;img src="https://camo.githubusercontent.com/66ed72167734f7abf785b1eb0af437538cd34ab7c421bf3088ff927c979a4b85/68747470733a2f2f7472617669732d63692e6f72672f637970726973732f6d75746174696f6e732e7376673f6272616e63683d6d6173746572" alt="Build Status"&gt;&lt;/a&gt;
&lt;a href="https://codeclimate.com/github/cypriss/mutations" rel="nofollow"&gt;&lt;img src="https://camo.githubusercontent.com/892e01edc9ea99aa9df47bc0da6eb2360976ec5fc39a22fc21a2d840d25bb76f/68747470733a2f2f636f6465636c696d6174652e636f6d2f6769746875622f637970726973732f6d75746174696f6e732e737667" alt="Code Climate"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Compose your business logic into commands that sanitize and validate input. Write safe, reusable, and maintainable code for Ruby and Rails apps.&lt;/p&gt;
&lt;h2&gt;
Installation&lt;/h2&gt;
&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;&lt;pre class="notranslate"&gt;&lt;code&gt;gem install mutations
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Or add it to your Gemfile:&lt;/p&gt;
&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;&lt;pre class="notranslate"&gt;&lt;code&gt;gem 'mutations'
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;
Example&lt;/h2&gt;
&lt;div class="highlight highlight-source-ruby notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-c"&gt;# Define a command that signs up a user.&lt;/span&gt;
&lt;span class="pl-k"&gt;class&lt;/span&gt; &lt;span class="pl-v"&gt;UserSignup&lt;/span&gt; &amp;lt; &lt;span class="pl-v"&gt;Mutations&lt;/span&gt;::&lt;span class="pl-v"&gt;Command&lt;/span&gt;
  &lt;span class="pl-c"&gt;# These inputs are required&lt;/span&gt;
  &lt;span class="pl-en"&gt;required&lt;/span&gt; &lt;span class="pl-k"&gt;do&lt;/span&gt;
    &lt;span class="pl-en"&gt;string&lt;/span&gt; &lt;span class="pl-pds"&gt;:email&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt; &lt;span class="pl-pds"&gt;matches&lt;/span&gt;: &lt;span class="pl-c1"&gt;EMAIL_REGEX&lt;/span&gt;
    &lt;span class="pl-en"&gt;string&lt;/span&gt; &lt;span class="pl-pds"&gt;:name&lt;/span&gt;
  &lt;span class="pl-k"&gt;end&lt;/span&gt;

  &lt;span class="pl-c"&gt;# These inputs are optional&lt;/span&gt;
  &lt;span class="pl-en"&gt;optional&lt;/span&gt; &lt;span class="pl-k"&gt;do&lt;/span&gt;
    &lt;span class="pl-en"&gt;boolean&lt;/span&gt; &lt;span class="pl-pds"&gt;:newsletter_subscribe&lt;/span&gt;
  &lt;span class="pl-k"&gt;end&lt;/span&gt;

  &lt;span class="pl-c"&gt;# The execute method is called only if the inputs validate. It does your business action.&lt;/span&gt;
  &lt;span class="pl-k"&gt;def&lt;/span&gt; &lt;span class="pl-en"&gt;execute&lt;/span&gt;
    &lt;span class="pl-s1"&gt;user&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-v"&gt;User&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;create!&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-en"&gt;inputs&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;
    &lt;span class="pl-v"&gt;NewsletterSubscriptions&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;create&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-pds"&gt;email&lt;/span&gt;: &lt;span class="pl-en"&gt;email&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt; &lt;span class="pl-pds"&gt;user_id&lt;/span&gt;: &lt;span class="pl-s1"&gt;user&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;id&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt; &lt;span class="pl-k"&gt;if&lt;/span&gt; &lt;span class="pl-en"&gt;newsletter_subscribe&lt;/span&gt;
    &lt;span class="pl-v"&gt;UserMailer&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;async&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-pds"&gt;:deliver_welcome&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt; &lt;span class="pl-s1"&gt;user&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;id&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;
    &lt;span class="pl-s1"&gt;user&lt;/span&gt;
  &lt;span class="pl-k"&gt;end&lt;/span&gt;
&lt;span class="pl-k"&gt;end&lt;/span&gt;

&lt;span class="pl-c"&gt;# In a controller action (for instance), you can run it:&lt;/span&gt;
&lt;span class="pl-k"&gt;def&lt;/span&gt; &lt;span class="pl-en"&gt;create&lt;/span&gt;
  &lt;span class="pl-s1"&gt;outcome&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-v"&gt;UserSignup&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;run&lt;/span&gt;&lt;/pre&gt;…
&lt;/div&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/cypriss/mutations"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h3&gt;
  
  
  hanami/hanami:
&lt;/h3&gt;

&lt;p&gt;Hanami não é uma gem de interactor e sim um framework web completo, como o Rails é. Mas ele traz internamente já uma solução do padrão para uso opcional por quem preferir seguir essa estratégia. Kudos para o pessoal do Hanami.&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--A9-wwsHG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/hanami"&gt;
        hanami
      &lt;/a&gt; / &lt;a href="https://github.com/hanami/hanami"&gt;
        hanami
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      The web, with simplicity.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;h1&gt;
Hanami 🌸
&lt;/h1&gt;
&lt;p&gt;The web, with simplicity.&lt;/p&gt;
&lt;h2&gt;
Version&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;This branch contains the code for &lt;code&gt;hanami&lt;/code&gt; 2.0.x.&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;
Frameworks&lt;/h2&gt;
&lt;p&gt;Hanami is a &lt;strong&gt;full-stack&lt;/strong&gt; Ruby web framework. It's made up of smaller, single-purpose libraries.&lt;/p&gt;
&lt;p&gt;This repository is for the full-stack framework, which provides the glue that ties all the parts together:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/hanami/router"&gt;&lt;strong&gt;Hanami::Router&lt;/strong&gt;&lt;/a&gt; - Rack compatible HTTP router for Ruby&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/hanami/controller"&gt;&lt;strong&gt;Hanami::Controller&lt;/strong&gt;&lt;/a&gt; - Full featured, fast and testable actions for Rack&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/hanami/view"&gt;&lt;strong&gt;Hanami::View&lt;/strong&gt;&lt;/a&gt; - Presentation with a separation between views and templates&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/hanami/helpers"&gt;&lt;strong&gt;Hanami::Helpers&lt;/strong&gt;&lt;/a&gt; - View helpers for Ruby applications&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/hanami/mailer"&gt;&lt;strong&gt;Hanami::Mailer&lt;/strong&gt;&lt;/a&gt; - Mail for Ruby applications&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/hanami/assets"&gt;&lt;strong&gt;Hanami::Assets&lt;/strong&gt;&lt;/a&gt; - Assets management for Ruby&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These components are designed to be used independently or together in a Hanami application.&lt;/p&gt;
&lt;h2&gt;
Status&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://badge.fury.io/rb/hanami" rel="nofollow"&gt;&lt;img src="https://camo.githubusercontent.com/670381120787b80e90aa0d2fca6e9097a2c0ff0e37c5172fa8f83fd1d23d455c/68747470733a2f2f62616467652e667572792e696f2f72622f68616e616d692e737667" alt="Gem Version"&gt;&lt;/a&gt;
&lt;a href="https://github.com/hanami/hanami/actions?query=workflow%3Aci+branch%3Amain"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--urmNt7G---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://github.com/hanami/hanami/workflows/ci/badge.svg%3Fbranch%3Dmain" alt="CI"&gt;&lt;/a&gt;
&lt;a href="https://depfu.com/github/hanami/hanami?project=Bundler" rel="nofollow"&gt;&lt;img src="https://camo.githubusercontent.com/3a6d39578b7809c6eda07cc0df993f5bfb763dca062e13530a65b3e6bae1fe8c/68747470733a2f2f6261646765732e64657066752e636f6d2f6261646765732f62613030306530663639653665663163343463643330333863616161313834312f6f766572766965772e737667" alt="Depfu"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
Installation&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Hanami&lt;/strong&gt; supports Ruby (MRI) 3.0+&lt;/p&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;gem install hanami&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
Usage&lt;/h2&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;hanami new bookshelf
&lt;span class="pl-c1"&gt;cd&lt;/span&gt; bookshelf &lt;span class="pl-k"&gt;&amp;amp;&amp;amp;&lt;/span&gt; bundle
bundle &lt;span class="pl-c1"&gt;exec&lt;/span&gt; hanami server &lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; visit http://localhost:2300&lt;/span&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Please follow along with the &lt;a href="https://guides.hanamirb.org/getting-started/" rel="nofollow"&gt;Getting Started guide&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;
Donations&lt;/h2&gt;
&lt;p&gt;You can give back to Open Source…&lt;/p&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/hanami/hanami"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h3&gt;
  
  
  Outras
&lt;/h3&gt;

&lt;p&gt;A seguir coloquei as que eu já li as documentações, considerei uso, mas por um ou outro motivo não decidi testar em algum projeto:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--A9-wwsHG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/Freshly"&gt;
        Freshly
      &lt;/a&gt; / &lt;a href="https://github.com/Freshly/flow"&gt;
        flow
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Write modular and reusable business logic that's understandable and maintainable.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;h1&gt;
Flow&lt;/h1&gt;
&lt;p&gt;&lt;a href="https://badge.fury.io/rb/flow" rel="nofollow"&gt;&lt;img src="https://camo.githubusercontent.com/02a9e4a7d01abaff4864e5eb633a6046d5ca66bb35f7ad1977bb9082a260efea/68747470733a2f2f62616467652e667572792e696f2f72622f666c6f772e737667" alt="Gem Version"&gt;&lt;/a&gt;
&lt;a href="https://semaphoreci.com/freshly/flow" rel="nofollow"&gt;&lt;img src="https://camo.githubusercontent.com/40bfabec69a56a95b8ba3ea4d100936812ce785280b75ea38d88734b46f7ba8b/68747470733a2f2f73656d6170686f726563692e636f6d2f6170692f76312f66726573686c792f666c6f772f6272616e636865732f6d61696e2f62616467652e737667" alt="Build Status"&gt;&lt;/a&gt;
&lt;a href="https://codeclimate.com/github/Freshly/flow/maintainability" rel="nofollow"&gt;&lt;img src="https://camo.githubusercontent.com/2125796e775e14ca3a8eed499e48b844209e1edff2a3b4ab91a6051e445c6ee8/68747470733a2f2f6170692e636f6465636c696d6174652e636f6d2f76312f6261646765732f30323133313635383030356231306332383965302f6d61696e7461696e6162696c697479" alt="Maintainability"&gt;&lt;/a&gt;
&lt;a href="https://codeclimate.com/github/Freshly/flow/test_coverage" rel="nofollow"&gt;&lt;img src="https://camo.githubusercontent.com/18e7973b2cba4e32d9c5b1040f771efbcf7b8663b8888743ade4d9dce8cebe89/68747470733a2f2f6170692e636f6465636c696d6174652e636f6d2f76312f6261646765732f30323133313635383030356231306332383965302f746573745f636f766572616765" alt="Test Coverage"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
Installation&lt;/h2&gt;
&lt;p&gt;Add this line to your application's Gemfile:&lt;/p&gt;
&lt;div class="highlight highlight-source-ruby notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-en"&gt;gem&lt;/span&gt; &lt;span class="pl-s"&gt;"flow"&lt;/span&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Then, in your project directory:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;$ bundle install
$ rails generate flow:install&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
What is Flow?&lt;/h2&gt;
&lt;p&gt;Flow is a &lt;a href="https://en.wikipedia.org/wiki/SOLID" rel="nofollow"&gt;SOLID&lt;/a&gt; implementation of the &lt;a href="https://en.wikipedia.org/wiki/Command_pattern" rel="nofollow"&gt;Command Pattern&lt;/a&gt; for Ruby on Rails.&lt;/p&gt;
&lt;p&gt;Flows allow you to encapsulate your application's &lt;a href="http://en.wikipedia.org/wiki/Business_logic" rel="nofollow"&gt;business logic&lt;/a&gt; into a set of extensible and reusable objects.&lt;/p&gt;
&lt;h2&gt;
Quickstart Example&lt;/h2&gt;
&lt;p&gt;Install Flow to your Rails project:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;$ rails generate flow:install&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Then define &lt;code&gt;State&lt;/code&gt;, &lt;code&gt;Operation&lt;/code&gt;(s), and &lt;code&gt;Flow&lt;/code&gt; objects.&lt;/p&gt;
&lt;h3&gt;
State&lt;/h3&gt;
&lt;p&gt;A &lt;code&gt;State&lt;/code&gt; object defines data that is to be read or written in &lt;code&gt;Operation&lt;/code&gt; objects throughout the &lt;code&gt;Flow&lt;/code&gt;. There are several types of data that can be defined, such as &lt;code&gt;argument&lt;/code&gt;, &lt;code&gt;option&lt;/code&gt;, and &lt;code&gt;output&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;$ rails generate flow:state Charge&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="highlight highlight-source-ruby notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-c"&gt;# app/states/charge_state.rb&lt;/span&gt;
&lt;span class="pl-k"&gt;class&lt;/span&gt; &lt;span class="pl-v"&gt;ChargeState&lt;/span&gt; &amp;lt; &lt;span class="pl-v"&gt;ApplicationState&lt;/span&gt;
  &lt;span class="pl-c"&gt;# @!attribute [r]&lt;/span&gt;
  &lt;span class="pl-c"&gt;# Order hash, readonly, required&lt;/span&gt;
  &lt;span class="pl-en"&gt;argument&lt;/span&gt; &lt;span class="pl-pds"&gt;:order&lt;/span&gt;
  &lt;span class="pl-c"&gt;# @!attribute [r]&lt;/span&gt;
  &lt;span class="pl-c"&gt;# User model instance readonly, required&lt;/span&gt;
  &lt;span class="pl-en"&gt;argument&lt;/span&gt; &lt;span class="pl-pds"&gt;:user&lt;/span&gt;

  &lt;span class="pl-c"&gt;# @!attribute&lt;/span&gt;&lt;/pre&gt;…
&lt;/div&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/Freshly/flow"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;br&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--A9-wwsHG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/serradura"&gt;
        serradura
      &lt;/a&gt; / &lt;a href="https://github.com/serradura/u-case"&gt;
        u-case
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Represent use cases in a simple and powerful way while writing modular, expressive and sequentially logical code.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;p&gt;
  &lt;a rel="noopener noreferrer" href="https://github.com/serradura/u-case./assets/ucase_logo_v1.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--D1sBlHZz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://github.com/serradura/u-case./assets/ucase_logo_v1.png" alt="u-case - Represent use cases in a simple and powerful way while writing modular, expressive and sequentially logical code."&gt;&lt;/a&gt;
  &lt;/p&gt;
&lt;p&gt;&lt;i&gt;Represent use cases in a simple and powerful way while writing modular, expressive and sequentially logical code.&lt;/i&gt;&lt;/p&gt;
  &lt;br&gt;
&lt;p&gt;
  &lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/9bf772b3184de491674d437904fbf9eb636e6af5cbadadedb588de99b83723b7/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f727562792d3e253344253230322e322e302d727562792e7376673f636f6c6f72413d39393030346426636f6c6f72423d636330303636"&gt;&lt;img src="https://camo.githubusercontent.com/9bf772b3184de491674d437904fbf9eb636e6af5cbadadedb588de99b83723b7/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f727562792d3e253344253230322e322e302d727562792e7376673f636f6c6f72413d39393030346426636f6c6f72423d636330303636" alt="Ruby"&gt;&lt;/a&gt;
  &lt;a href="https://rubygems.org/gems/u-case" rel="nofollow"&gt;
    &lt;img alt="Gem" src="https://camo.githubusercontent.com/198dde9438d7350fa1814b9a92271df6e7d728695097e70cbfa53d20449131ed/68747470733a2f2f696d672e736869656c64732e696f2f67656d2f762f752d636173652e7376673f7374796c653d666c61742d737175617265"&gt;
  &lt;/a&gt;
  &lt;a href="https://github.com/serradura/u-case/actions/workflows/ci.yml"&gt;
    &lt;img alt="Build Status" src="https://res.cloudinary.com/practicaldev/image/fetch/s--sKfsj4ew--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://github.com/serradura/u-case/actions/workflows/ci.yml/badge.svg"&gt;
  &lt;/a&gt;
  &lt;a href="https://codeclimate.com/github/serradura/u-case/maintainability" rel="nofollow"&gt;
    &lt;img alt="Maintainability" src="https://camo.githubusercontent.com/02a73e530bcd3cb50b93495ecfeccaddfb045ba21b8fc4409af56947726cb498/68747470733a2f2f6170692e636f6465636c696d6174652e636f6d2f76312f6261646765732f35633363386164316230623934336638386566642f6d61696e7461696e6162696c697479"&gt;
  &lt;/a&gt;
  &lt;a href="https://codeclimate.com/github/serradura/u-case/test_coverage" rel="nofollow"&gt;
    &lt;img alt="Test Coverage" src="https://camo.githubusercontent.com/ef9eaeec96232cbf38492c239ae785a840b2938f56491469a8f6ced32a18a25e/68747470733a2f2f6170692e636f6465636c696d6174652e636f6d2f76312f6261646765732f35633363386164316230623934336638386566642f746573745f636f766572616765"&gt;
  &lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;The main project goals are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Easy to use and easy to learn (input &lt;strong&gt;&amp;gt;&amp;gt;&lt;/strong&gt; process &lt;strong&gt;&amp;gt;&amp;gt;&lt;/strong&gt; output).&lt;/li&gt;
&lt;li&gt;Promote immutability (transforming data instead of modifying it) and data integrity.&lt;/li&gt;
&lt;li&gt;No callbacks (ex: before, after, around) to avoid code indirections that could compromise the state and understanding of application flows.&lt;/li&gt;
&lt;li&gt;Solve complex business logic, by allowing the composition of use cases (flow creation).&lt;/li&gt;
&lt;li&gt;Be fast and optimized (Check out the &lt;a href="https://github.com/serradura/u-case#benchmarks"&gt;benchmarks&lt;/a&gt; section).&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Check out the repo &lt;a href="https://github.com/serradura/from-fat-controllers-to-use-cases"&gt;https://github.com/serradura/from-fat-controllers-to-use-cases&lt;/a&gt; to see a Rails application that uses this gem to handle its business logic.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
Documentation &lt;/h2&gt;
&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Version&lt;/th&gt;
&lt;th&gt;Documentation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;unreleased&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/serradura/u-case/blob/main/README.md"&gt;https://github.com/serradura/u-case/blob/main/README.md&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4.5.1&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/serradura/u-case/blob/v4.x/README.md"&gt;https://github.com/serradura/u-case/blob/v4.x/README.md&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3.1.0&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/serradura/u-case/blob/v3.x/README.md"&gt;https://github.com/serradura/u-case/blob/v3.x/README.md&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2.6.0&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/serradura/u-case/blob/v2.x/README.md"&gt;https://github.com/serradura/u-case/blob/v2.x/README.md&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1.1.0&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/serradura/u-case/blob/v1.x/README.md"&gt;https://github.com/serradura/u-case/blob/v1.x/README.md&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Você entende português? 🇧🇷 🇵🇹 Verifique o &lt;a href="https://github.com/serradura/u-case/blob/main/README.pt-BR.md"&gt;README traduzido em pt-BR&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
Table of Contents &lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/serradura/u-case#compatibility"&gt;Compatibility&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/serradura/u-case#dependencies"&gt;Dependencies&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/serradura/u-case#installation"&gt;Installation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/serradura/u-case#usage"&gt;Usage&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/serradura/u-case#microcase---how-to-define-a-use-case"&gt;&lt;code&gt;Micro::Case&lt;/code&gt; - How to define a use case?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/serradura/u-case#microcaseresult---what-is-a-use-case-result"&gt;&lt;code&gt;Micro::Case::Result&lt;/code&gt; -&lt;/a&gt;…&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/serradura/u-case"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;br&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--A9-wwsHG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/apneadiving"&gt;
        apneadiving
      &lt;/a&gt; / &lt;a href="https://github.com/apneadiving/waterfall"&gt;
        waterfall
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A slice of functional programming to chain ruby services and blocks, thus providing a new approach to flow control. Make them flow!
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;p&gt;&lt;a href="https://codeclimate.com/github/apneadiving/waterfall" rel="nofollow"&gt;&lt;img src="https://camo.githubusercontent.com/80d6721c09430cfed3bd59bdd26e077f1d10aedb5d0dae6fb4c171ffa43a56ae/68747470733a2f2f636f6465636c696d6174652e636f6d2f6769746875622f61706e6561646976696e672f776174657266616c6c2f6261646765732f6770612e737667" alt="Code Climate"&gt;&lt;/a&gt;
&lt;a href="https://codeclimate.com/github/apneadiving/waterfall/coverage" rel="nofollow"&gt;&lt;img src="https://camo.githubusercontent.com/00928f2bad51dff1d2d0e54cca0a30235255b597b527cebf788684175f670fdc/68747470733a2f2f636f6465636c696d6174652e636f6d2f6769746875622f61706e6561646976696e672f776174657266616c6c2f6261646765732f636f7665726167652e737667" alt="Test Coverage"&gt;&lt;/a&gt;
&lt;a href="https://travis-ci.org/apneadiving/waterfall" rel="nofollow"&gt;&lt;img src="https://camo.githubusercontent.com/66193942ea39e6952651c0247d0785c5de3e3f56bcadc187629460f1a91598c4/68747470733a2f2f7472617669732d63692e6f72672f61706e6561646976696e672f776174657266616c6c2e7376673f6272616e63683d6d6173746572" alt="Build Status"&gt;&lt;/a&gt;
&lt;a href="https://badge.fury.io/rb/waterfall" rel="nofollow"&gt;&lt;img src="https://camo.githubusercontent.com/4b3bebd11a25b1356749f69b5d80454dd47727f22d93b684e870cb88a2df0651/68747470733a2f2f62616467652e667572792e696f2f72622f776174657266616c6c2e737667" alt="Gem Version"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;
Goal&lt;/h4&gt;
&lt;p&gt;Chain ruby commands, and treat them like a flow, which provides a new approach to application control flow.&lt;/p&gt;
&lt;p&gt;When logic is complicated, waterfalls show their true power and let you write intention revealing code. Above all they excel at chaining services.&lt;/p&gt;
&lt;h4&gt;
Material&lt;/h4&gt;
&lt;p&gt;&lt;a href="https://leanpub.com/the-unhappy-path" rel="nofollow"&gt; &lt;img width="80" height="116" src="https://camo.githubusercontent.com/e4ebaab1ae3068427b48a05dfb4416281f9cd67e0ad338fa522de5ff482dad5a/68747470733a2f2f61706e6561646976696e672e6769746875622e696f2f696d616765732f756e68617070792d706174682e706e67"&gt; &lt;/a&gt;
Upcoming book about failure management patterns, leveraging the gem: &lt;a href="https://leanpub.com/the-unhappy-path" rel="nofollow"&gt;The Unhappy path&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;General presentation blog post there: &lt;a href="https://medium.com/p/chain-service-objects-like-a-boss-35d0b83606ab" rel="nofollow"&gt;Chain services objects like a boss&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Reach me &lt;a href="https://twitter.com/apneadiving" rel="nofollow"&gt;@apneadiving&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;
Overview&lt;/h4&gt;
&lt;p&gt;A waterfall object has its own flow of commands, you can chain your commands and if something wrong happens, you dam the flow which bypasses the rest of the commands.&lt;/p&gt;
&lt;p&gt;Here is a basic representation:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;green, the flow goes on, &lt;code&gt;chain&lt;/code&gt; by &lt;code&gt;chain&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;red its bypassed and only &lt;code&gt;on_dam&lt;/code&gt; blocks are executed.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/6d8948c18d577e3fb54828556e346a84b6a35f88fddb79aab85cb4e9be9a04db/68747470733a2f2f61706e6561646976696e672e6769746875622e696f2f696d616765732f776174657266616c6c5f7072696e6369706c652e706e67"&gt;&lt;img src="https://camo.githubusercontent.com/6d8948c18d577e3fb54828556e346a84b6a35f88fddb79aab85cb4e9be9a04db/68747470733a2f2f61706e6561646976696e672e6769746875622e696f2f696d616765732f776174657266616c6c5f7072696e6369706c652e706e67" alt="Waterfall Principle"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;
Example&lt;/h4&gt;
&lt;div class="highlight highlight-source-ruby notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-k"&gt;class&lt;/span&gt; &lt;span class="pl-v"&gt;FetchUser&lt;/span&gt;
  &lt;span class="pl-en"&gt;include&lt;/span&gt; &lt;span class="pl-v"&gt;Waterfall&lt;/span&gt;
  &lt;span class="pl-k"&gt;def&lt;/span&gt; &lt;span class="pl-en"&gt;initialize&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s1"&gt;user_id&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;
    &lt;span class="pl-c1"&gt;@user_id&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-s1"&gt;user_id&lt;/span&gt;
  &lt;span class="pl-k"&gt;end&lt;/span&gt;
  &lt;span class="pl-k"&gt;def&lt;/span&gt; &lt;span class="pl-en"&gt;call&lt;/span&gt;
    &lt;span class="pl-en"&gt;chain&lt;/span&gt; &lt;span class="pl-kos"&gt;{&lt;/span&gt; &lt;span class="pl-c1"&gt;@response&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-v"&gt;HTTParty&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;get&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s"&gt;"https://jsonplaceholder.typicode.com/users/&lt;span class="pl-s1"&gt;&lt;span class="pl-kos"&gt;#{&lt;/span&gt;&lt;span class="pl-c1"&gt;@user_id&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;…
&lt;/div&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/apneadiving/waterfall"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;br&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--A9-wwsHG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/pabloh"&gt;
        pabloh
      &lt;/a&gt; / &lt;a href="https://github.com/pabloh/pathway"&gt;
        pathway
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Define your business logic in simple steps
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;h1&gt;
Pathway&lt;/h1&gt;
&lt;p&gt;&lt;a href="https://badge.fury.io/rb/pathway" rel="nofollow"&gt;&lt;img src="https://camo.githubusercontent.com/5440f35b43ab39a7f0e0acf7114fef8b61dd9b6c79af4009df0bc3701cbc0068/68747470733a2f2f62616467652e667572792e696f2f72622f706174687761792e737667" alt="Gem Version"&gt;&lt;/a&gt;
&lt;a href="https://github.com/oabloh/pathway/actions?query=workflow%3ATests"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--6PBs_yTM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://github.com/pabloh/pathway/workflows/Tests/badge.svg" alt="Tests"&gt;&lt;/a&gt;
&lt;a href="https://coveralls.io/github/pabloh/pathway?branch=master" rel="nofollow"&gt;&lt;img src="https://camo.githubusercontent.com/ca871dadeaca3eb16fa3833acb77856e95b8182455cb841687d924860321b5f4/68747470733a2f2f636f766572616c6c732e696f2f7265706f732f6769746875622f7061626c6f682f706174687761792f62616467652e7376673f6272616e63683d6d6173746572" alt="Coverage Status"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Pathway encapsulates your business logic into simple operation objects (AKA application services on the &lt;a href="https://en.wikipedia.org/wiki/Domain-driven_design" rel="nofollow"&gt;DDD&lt;/a&gt; lingo).&lt;/p&gt;
&lt;h2&gt;
Installation&lt;/h2&gt;
&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;&lt;pre class="notranslate"&gt;&lt;code&gt;$ gem install pathway
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;
Description&lt;/h2&gt;
&lt;p&gt;Pathway helps you separate your business logic from the rest of your application; regardless of is an HTTP backend, a background processing daemon, etc
The main concept Pathway relies upon to build domain logic modules is the operation, this important concept will be explained in detail in the following sections.&lt;/p&gt;
&lt;p&gt;Pathway also aims to be easy to use, stay lightweight and extensible (by the use of plugins), avoid unnecessary dependencies, keep the core classes clean from monkey patching and help yield an organized and uniform codebase.&lt;/p&gt;
&lt;h2&gt;
Usage&lt;/h2&gt;
&lt;h3&gt;
Main concepts and API&lt;/h3&gt;
&lt;p&gt;As mentioned earlier the operation is an essential concept Pathway is built around. Operations not only structure your code (using steps as will be explained later) but also express meaningful business actions. Operations can be thought…&lt;/p&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/pabloh/pathway"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;h2&gt;
  
  
  Conclusão
&lt;/h2&gt;

&lt;p&gt;Esse foi um resumo bem geral sobre o padrão interactor, com mais exemplos práticos do que teorias propriamente ditas, mas que de toda forma ajuda a apresentar os conceitos, o uso e incentiva os desenvolvedores começarem a testar nos seus projetos pessoais ou naqueles testes técnicos para seleção de candidatos. Mas vale lembrar sempre: como tudo no desenvolvimento de software, não há bala de prata, esse padrão apresentado não vai resolver todos os problemas e nem será a melhor opção para todos os casos que aparecerem, mas vale a pena conhecer mais para enriquecer o portifólio de estratégias e soluções poderão ser útil ao desenvolvedor em algum momento da carreira.&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>designpatterns</category>
      <category>interactor</category>
    </item>
    <item>
      <title>Meu processo de atualização do Ruby on Rails</title>
      <dc:creator>Stephann</dc:creator>
      <pubDate>Sat, 19 Mar 2022 23:22:58 +0000</pubDate>
      <link>https://dev.to/stephann/como-atualizei-o-rails-antigo-para-o-mais-recente-557p</link>
      <guid>https://dev.to/stephann/como-atualizei-o-rails-antigo-para-o-mais-recente-557p</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Esse post foi escrito em janeiro de 2021 no meu blog e a atualização do Rails ocorreu entre outubro/novembro de 2020. O Ruby 2.7 e o Rails 6.1 eram as versões mais recentes na época. Mas o que está descrito nesse post se mantém válido e acho que valerá pro Rails 8, Rails 9 e assim por diante.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Introdução
&lt;/h2&gt;

&lt;p&gt;No apagar das luzes de 2020, colocamos a versão do Rails 6.1.0 em produção aqui na firma. Essa informação por si só não teria uma relevância tão grande a ponto de merecer um post falando sobre, mas o fato de termos atualizado o Ruby 2.4 para a 2.7 e o Rails 4.2 para a versão 6.1, tornou o trabalho complexo e digno do compartilhamento de experiência com os demais desenvolvedores e equipes que planejam encarar o mesmo desafio e atualizar suas aplicações.&lt;/p&gt;

&lt;p&gt;Há vários artigos falando e explicando sobre como atualizar o Rails, e o que vou relatar nesse texto será um apanhado de técnicas desses artigos que li e que funcionou pra mim, e que pode não ser necessariamente a melhor estratégia mas garanto que vai iluminar bastante o teu caminho. De toda forma, sugiro que também leia relatos de vários outros desenvolvedores caso esteja pensando em atualizar o Rails e encontre o que melhor se encaixa para o seu caso.&lt;/p&gt;

&lt;h2&gt;
  
  
  O que tornou tudo menos difícil
&lt;/h2&gt;

&lt;p&gt;Realizar uma atualização tão drástica em uma aplicação já é uma tarefa complexa, mas nem sei dizer o quão mais complexo seria caso a gente não tivesse uma quantidade razoável de testes unitários cobrindo boa parte das funcionalidades do sistema. Ver os testes do CI passando, tudo verdinho, foi o que me fez prosseguir na aplicação com mais confiança pois eu sabia que pelo menos aquilo que foi testado ainda estava funcionando. Então fica a dica: Implementem testes e solicitem implementação de testes ao fazer revisão de código nas atividades de terceiros.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mas primeiro...
&lt;/h2&gt;

&lt;p&gt;Antes de começar a atualizar o Rails, resolvi atualizar o Ruby, pois há dependências que dependem de um Ruby mais novo, como o Rails 6 por exemplo, que pede pelo menos a versão 2.5 do Ruby.&lt;/p&gt;

&lt;p&gt;Nós estávamos na versão 2.4, que não é tão defasada assim como uma 1.9~2.1, mas que já deixou de ser mantida e não recebe mais correções, nem de segurança. Então criei um branch separado e atualizei para a versão 2.7.1 (até então não tinha saído a versão 2.7.2 e nem a 3.0). A atualização foi simples, não precisou bulir em nada além de um remendo de macaco (monkey patch) que fiz prum teste específico passar mas que foi removido posteriormente por não ser mais necessário.&lt;/p&gt;

&lt;p&gt;Depois de fazer um QA mais geral na plataforma com o Ruby mais novo, colocamos a atualização em produção e não houve nenhum transtorno, correu tudo tranquilo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Organizando a bagunça do Gemfile
&lt;/h2&gt;

&lt;p&gt;Não sei como é nas aplicações que vocês trabalham, mas em algumas que trabalhei, inclusive aqui na firma, o Gemfile estava bastante bagunçado, sem um padrão na organização das gems e suas versões. Então comecei organizando o Gemfile com 2 pontos em mente:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;As gems serão ordenadas por nome.&lt;/li&gt;
&lt;li&gt;As versões das gems serão travadas (nada mais de &lt;code&gt;~2&lt;/code&gt; ou &lt;code&gt;&amp;gt;= 2.1&lt;/code&gt; e essas coisas).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A organização por nome foi baseada na configuração padrão do Rubocop e é por estética mesmo, e também pra tirar da mão do desenvolvedor a responsabilidade de saber em que grupinho de dependências aquela gem se encaixa ou se vai em cima ou vai em baixo. Então organizando por nome fica combinado de todo mundo seguir um mesmo estilo. A organização é mais ou menos assim:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# ⛔️ RUIM&lt;/span&gt;
&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'g'&lt;/span&gt;
&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'d'&lt;/span&gt;

&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'a'&lt;/span&gt;

&lt;span class="n"&gt;group&lt;/span&gt; &lt;span class="ss"&gt;:development&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
 &lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'f'&lt;/span&gt;
 &lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'c'&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'b'&lt;/span&gt;

&lt;span class="c1"&gt;# ✅ BOM&lt;/span&gt;
&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'a'&lt;/span&gt;
&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'b'&lt;/span&gt;
&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'d'&lt;/span&gt;
&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'g'&lt;/span&gt;

&lt;span class="n"&gt;group&lt;/span&gt; &lt;span class="ss"&gt;:development&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'c'&lt;/span&gt;
  &lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'f'&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;E o motivo de travar as versões das gems é porque eu não queria  que uma gem fosse atualizada por tabela quando eu estivesse atualizando uma outra dependência. Eu só queria que ela fosse atualizada quando eu fosse no Gemfile e alterasse a versão dela na mão. Então o que tava assim:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'carrierwave'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'~&amp;gt; 1.0'&lt;/span&gt;
&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'simple_form'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ficou assim:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'carrierwave'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'1.2.3'&lt;/span&gt;
&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'simple_form'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'3.3.1'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Para saber quais a versões correspondentes de cada dependência eu tive que olhar no &lt;code&gt;Gemfile.lock&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  O trabalho começa agora
&lt;/h2&gt;

&lt;p&gt;Depois do Ruby atualizado, o Gemfile organizado e as depêndencias com suas versões devidamente travadas, eu pude começar de fato a atualização do Rails. O plano era colocar o Rails na versão 6.0, mas isso mudou durante a atualização pois foi lançada a versão 6.1.0.rc1 e semanas depois, a versão 6.1.0 final.&lt;/p&gt;

&lt;p&gt;Como estávamos na versão 4.2, resolvi atualizar incrementalmente, ou seja, não pular da 4.2 direto pra versão 6.1, e sim atualizando incrementalmente, aos poucos, primeiro pra 5.0, depois pra 5.1, pra 5.2, pra 6.0 e por fim, pra 6.1. Pra cada versão nova dessa, eu repeti um ciclo de ações até chegar na versão mais recente. As ações foram:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Instalar a próxima versão do Rails
&lt;/h3&gt;

&lt;p&gt;Nesse passo, eu mudava o Gemfile para configurar a próxima versão do Rails. Como no primeiro ciclo eu estava na versão 4.2, então o Gemfile ficou assim: &lt;code&gt;gem 'rails', '5.0.7.2'&lt;/code&gt;. Quando iniciei o ciclo novamente já foi pra atualizar para a 5.1.7 e assim por diante. Pra saber as versões disponíveis do Rails, eu conferi nesse link aqui do Rubygems: &lt;a href="https://rubygems.org/gems/rails/versions"&gt;https://rubygems.org/gems/rails/versions&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Com a versão seguinte configurada no Gemfile, o comando &lt;code&gt;bundle update&lt;/code&gt; simplesmente não funcionava, mostrava o erro: &lt;code&gt;Bundler could not find compatible version for gem "XYZ"&lt;/code&gt;. Eu já tinha uma experiência prévia com atualização do Rails, então sabia que isso ia me levar pra uma arapuca onde eu iria atualizar inúmeras versões de gems conforme os erros fossem aparecendo e não ia resolver o problema. Então o que funcionou pra mim foi comentar todas as gems do Gemfile, exceto o Rails, rodar o &lt;code&gt;bundle update&lt;/code&gt; para instalar o Rails novo com sucesso, depois ir descomentando as gems de pouco em pouco e rodando o &lt;code&gt;bundle install&lt;/code&gt;. Dessa forma apareceria erros de incompatibilidade de algumas dependências e eu teria que tratá-los.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Tratar as dependências incompatíveis
&lt;/h3&gt;

&lt;p&gt;Quando aparecia alguma gem incompatível, a incompatibilidade era com o novo Rails ou com alguma gem que eu tinha atualizado. Pra tratar essas incompatibilidades eu seguia um esquema mais ou menos assim:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Quando a gem era sobre alguma coisa simples, eu atualizava para a versão mais recente.&lt;/li&gt;
&lt;li&gt;Quando ela era importante e complexa, eu tentava atualizar para uma versão mais próxima que teve mudanças mais leves e que era compatível com o restante das dependências.&lt;/li&gt;
&lt;li&gt;Caso a única saída fosse uma atualização com mudanças bruscas, o jeito era atualizar e adequar a aplicação para que funcionasse corretamente com a nova versão da gem.&lt;/li&gt;
&lt;li&gt;Quando a gem estava abandonada, eu substituia por outra mais recente ou, quando era possível, eu implementava a solução dentro do projeto sem dependência ou fazia um fork e corrigia para ela se tornar compatível.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Resumindo, meu plano era rodar com sucesso um &lt;code&gt;bundle install&lt;/code&gt; com o mínimo de mudanças possíveis. E eu sempre ficava de olho nas notas de lançamento e histórico de mudança das gems que eu tivesse que atualizar. Essas informações podem ser geralmente encontradas na seção de releases do Github ou em arquivos CHANGELOG.md e MIGRATION.md que existem na maioria dos projetos.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Adequar o código para as novas versões
&lt;/h3&gt;

&lt;p&gt;Depois que o &lt;code&gt;bundle install&lt;/code&gt; foi executado com sucesso, cuidei pra adequar o projeto às novidades da versão recém instalada do Rails. Pra isso era necessário primeiro saber o que mudou, e essas informações eu encontrei nas próprias páginas do Rails. Foi importante ler tanto &lt;a href=""&gt;as notas de lançamento&lt;/a&gt; quanto &lt;a href=""&gt;os guias de atualização&lt;/a&gt;. Nessas páginas têm certinho o que foi removido, o que foi adicionado, o que foi modificado e como as aplicações devem se comportar após a atualização. Outra coisa que foi extremamente importante pra mim foi o &lt;a href=""&gt;RailsDiff&lt;/a&gt;, que é basicamente um site que compara os projetos gerados por cada versão do Rails, assim eu consegui ver, por exemplo, a diferença do resultado entre um &lt;code&gt;rails new example&lt;/code&gt; executado com o Rails 4.2 e outro executado com o Rails 5.0 e pude adequar as configurações do meu projeto.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Garantir que básico não está quebrando
&lt;/h3&gt;

&lt;p&gt;Depois que da versão seguinte do Rails estava instalada, as dependências que necessitavam de atualização foram atualizadas, o código foi adequado de acordo com as novidades, eu tinha que testar que o básico está funcionando.&lt;/p&gt;

&lt;p&gt;Começava com um &lt;code&gt;bundle exec rails c&lt;/code&gt; básico, depois um &lt;code&gt;bundle exec rails s&lt;/code&gt; e navegava em algumas telas, criava uma conta, realizava as ações principais da regra de negócio (ex.: realizar um pedido, cadastrar produto e etc) e depois eu rodava um &lt;code&gt;rspec&lt;/code&gt; em algum arquivo de testes pra ver se os testes conseguiam ser executados. Após garantir que o básico estava funcionando, eu mandava as alterações pro nosso CI pra ver se os testes continuavam passando.&lt;/p&gt;

&lt;p&gt;Ao realizar esses testes básicos, quase sempre apareciam alguns erros no console ou nos resultados dos testes que, às vezes, eram fáceis de corrigir com algum ajuste e às vezes apareciam erros que necessitavam de uma versão mais atualizada de uma gem pra serem corrigidos. Aí eu voltava para o segundo ponto desse ciclo para tratar as atualizações de dependências.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Fazer commits
&lt;/h3&gt;

&lt;p&gt;Quando eu garantia que o básico estava funcionando e o testes estavam passando, eu fazia um push pro Github pra ter um backup caso as próximas atualizações não saíssem como o esperado e eu tivesse que desfazer algo. Dessa forma fica fácil saber o que pode ser descartado com segurança sem o risco de perder algo que era importante para a atualização funcionar. E no fundo também era pra caso não fosse possível atualizar pra versão mais recente do Rails, aí eu teria o código da aplicação atualizada para o Rails mais recente que eu consegui atualizar.&lt;/p&gt;

&lt;p&gt;Assim meu branch de atualização ficou com os commits mais ou menos dessa forma:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Organizando o Gemfile&lt;/li&gt;
&lt;li&gt;Atualizando para o Rails 5.0&lt;/li&gt;
&lt;li&gt;Atualizando para o Rails 5.1&lt;/li&gt;
&lt;li&gt;Atualizando para o Rails 5.2&lt;/li&gt;
&lt;li&gt;...&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Com o código devidamente guardado, eu reiniciava o ciclo para atualizar para o próxima versão, e iterei até a versão 6.1.0.&lt;/p&gt;

&lt;h2&gt;
  
  
  Oportunidade de contribuir pro Rails
&lt;/h2&gt;

&lt;p&gt;O plano inicial era atualizar para o Rails 6.0 pois era a versão mais recente no momento, mas por sorte a 6.1 foi liberada como RC assim que eu estava finalizando a atividade. Pra não deixar o trabalho incompleto e já que eu estava com a mão na massa, a versão 6.1 entrou no escopo da tarefa e tive a oportunidade de ter contato com a nova versão no dia seguinte da publicação. E como é da natureza das versões &lt;em&gt;release candidates&lt;/em&gt; ser mais propensas a conter falhas, foi o que acabei encontrando.&lt;/p&gt;

&lt;p&gt;Havia uma incompatibilidade entre o web-console e a nova versão do Rails que me fez considerar ficar na 6.0 mesmo e esperar a 6.1 ficar mais estável, mas eu tava tão no gás de mexer e entender nas entranhas das gems que fui investigar o problema pra ver se eu conseguiria resolver. Acabei conseguindo e isso virou um &lt;a href="https://github.com/rails/web-console/pull/304"&gt;pull request&lt;/a&gt; pro web-console e &lt;a href="https://github.com/rails/rails/pull/40549"&gt;outro&lt;/a&gt; para o Rails. Já posso colocar no currículo que sou um &lt;em&gt;Rails contributor&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Atualizando as demais dependências
&lt;/h2&gt;

&lt;p&gt;Eu poderia ter parado na atualização do Rails, mas já que eu estava na chuva, eu ia me molhar &lt;em&gt;de com força&lt;/em&gt;. Parti pra atualizar as demais dependências mesmo que isso não fosse necessáro pra concluir a atualização do Rails, porque da mesma forma que o Rails defasado dificulta a evolução do nosso código, outras dependências acabam fazendo o mesmo mas numa escala menor.&lt;/p&gt;

&lt;p&gt;Minha estratégia não mudou muito da que usei pra atualizar o Rails, eu ia em cada gem, olhava no Rubygems as versões mais recentes, instalava, atualizava outras que fossem necessárias, lia os changelogs nos repositórios e adequava meu código, testava e realizava os push pro Github.&lt;/p&gt;

&lt;h2&gt;
  
  
  Removendo o que era desnecessário
&lt;/h2&gt;

&lt;p&gt;Nesse ponto eu já estava com um conhecimento bem maior de cada dependência do projeto e do que cada uma fazia, consequentemente eu conseguia identificar o que não estava sendo necessário ou o que poderia ser substituído por outra solução. Fiz uma limpa removendo gems não mais utilizadas e trocando algumas gems por uma solução própria levando o projeto a ficar com 27 dependências externas a menos.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pequenos ajustes
&lt;/h2&gt;

&lt;p&gt;Outra coisa que não era crucial pra atualização do Rails mas que foi bom fazer foi a organização algumas coisas do projeto que não eram necessariamente relacionadas à regra de negócio, eram mais sobre factories, testes, organização de módulos e etc. O projeto foi criado em 2010, ainda na época que o Rails 3.0.0 era o mais &lt;em&gt;on the edge&lt;/em&gt;, passou pela mão por algumas duzias de desenvolvedores nesse tempo que os padrões de organização de arquivos/código do framework ainda estavam amadurecendo. Por isso tive que fazer uma faxina em algumas coisas que estavam um pouco bagunçadas no nosso rails_helper.rb, nas factories, nos arquivos de suporte dos testes, na organização dos concerns e em outras coisas que não dessem tanto trabalho e mudassem muito a forma que o sistema se comportava.&lt;/p&gt;

&lt;h2&gt;
  
  
  Colocando no ar
&lt;/h2&gt;

&lt;p&gt;Com mais de 1.500 arquivos modificados, a atualização foi dada como "finalizada", mas ainda assim tivemos alguns bate e volta no QA pra corrigir os problemas que surgiram após um pente fino em quase todas as funcionalidades do sistema.&lt;/p&gt;

&lt;p&gt;Depois do QA minuncioso não detectar mais nenhum erro, colocamos a atualização em produção e, como a gente já esperava, apareceram mais uns probleminhas, mas nada muito sério, foram justamente em funcionalidades que não estavam cobertas por testes (olha aí a importância de implementar testes) e que também não tinha como ser validadas em ambiente de sandbox. Nosso coletor de erro nos auxiliou nesse momento e após alguns hotfixes podemos encher a boca pra dizer que estávamos com o Rails 6.1.0 e o Ruby 2.7.2 rodando em produção.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusão
&lt;/h2&gt;

&lt;p&gt;O sentimento é o mesmo de limpar a casa, fazer uma faxina pesada, comprar móveis novos e reorganizar cômodos. Mas como toda faxina, o projeto também precisa de uma limpeza frequente pra não chegar no ponto que estava. Pra isso é necessário implantar uma cultura de atualização contínua, de pagar débitos técnicos, de revisão de código, tudo para auxiliar o projeto a se manter mais atualizado e organizado possível.&lt;/p&gt;

&lt;p&gt;Aqui ainda não está definido como vamos fazer, mas o plano é ter o Dependabot, o serviço do Github que abre pull requests com atualizações de dependências, rodando uma rotina mensal e todo mês reservar algumas horas da equipe pra implementar e testar as atualizações. Esperamos que assim a gente nunca mais tenha que estar no ar com um Rails de 6 anos atrás. Já temos o Ruby 3.0 e o Rails 6.1.1 batendo na nossa porta. Mas isso fica pra um próximo capítulo.&lt;/p&gt;

</description>
      <category>rails</category>
      <category>ruby</category>
    </item>
  </channel>
</rss>
