Não foi possível carregar o Diqus. Se você é o moderador, por favor veja o nosso guia de problemas.
Eu gosto de utilizar a gem inherited resources. No caso do checkout, não acho que a action create deveria chamar Checkout.process e sim algo similar a Checkout.create, que acho que é o esperado. Como a action faria o esperado, as linhas poderiam ser apagadas e o a lógica delegada para o inherited resources.
Eu entendo que o método process faz mais coisas que somente criar algum recurso, mas eu teria estruturado esse código diferente. Me parece que com umas quantas poucas linhas a mais na classe Order todas aquelas linhas da classe Checkout poderiam ser apagadas. Eu lembro que voce mencionou que os modelos começam a ficar gordos, mas eu acho estranho ter um mailer chamado checkout que recebe um pedido e uma mensagem flash de checkout que fala também sobre o pedido, para mim claramente o que está sendo manipulado é um pedido e não um checkout.
Achei bem legal esse post para fazer refletir as pessoas, controllers complexos é o problema que mais acostumo ver em projetos.
Eu não gosto de coisas mágicas como inherited resources. Já trabalhei em um projeto que usava e era o caos instaurado. Desde então, evito qualquer coisa do tipo.
O ponto do artigo é exatamente mostrar que você não precisa colocar no modelo, como você sugere. Neste exemplo, não estou lidando com um recurso, mas com um processo/workflow. É um pattern chamado Interactor.
Novamente, não vejo problemas em utilizar o nome "checkout" como base para todo esse fluxo, já que é exatamente isso o que está acontecendo. Meu problema com controllers complexos é justamente esse; pensar que tudo pode ser um resource, quando na verdade não é. Não estamos criando um pedido, mas fazendo um processo de checkout, que envolve muito mais que apenas o registro na tabela orders.
Quanto ao nome do email, sim, poderia ter sido usado um nome melhor como order_confirmation, tanto que vou fazer essa alteração no artigo. :)
Ô Nando,
Posso sugerir uma gem pra se usar ao inves do inherited_resources?
https://github.com/before-a...
Por favor, da uma olhada e diz se voce acha se ajuda na reducao de complexidade
E quanto a organização dos arquivos? Quando o projeto cresce utilizar alguns namespaces se torna indispensável
Sim, concordo. Normalmente coloco isso em um namespace 'mediators' ou 'interactors'.
Olá Nando, eu utilizo classes de serviços, como por exemplo CustomerService, tipo uma caixa de método relacionados, porém esta classe fica gigante.
Também faço isso andresondanilo2, mas as minhas classes de serviço não ficam grandes pois sigo a linha do Nando. A classe checkout no meu caso seria Um CheckoutService ou CheckoutProccess e quanto maior for o fluxo mais classes eu teria e o CheckoutProccess trataria de fazer as chamadas. A vantagem é deixar o controller fazendo só o que deve fazer que é passar atributos da view para baixo.
Nando, bacana! também não vejo resources nesse processo. Para extrair a complexidade dos controllers, venho usando o conceito de Form Models e Service Object, pescando as dicas do livro “Growing Rails Applications in Practice” by Henning Koch and Thomas Eisenbarth.
Nando, só por curiosidade, como você testaria, no contexto do happy path, se o pedido foi salvo com os dados corretos? Entendo que a criação de um "order" é um detalhe de implementação, mas um efeito importante a se testar... uma maneira que estou vendo de fazer isso usando o código do exemplo (que não me parece tão correta), seria obtendo o Order do banco de dados (usando o order_id) e verificando se foi salvo com os dados corretos... ou modificando o código com um callback do tipo "on_success", onde privadamente se passaria o objeto "order" recém-criado para consultar se foi salvo com os dados corretos? Ou usando mocks?
Testaria após chamar Checkout.process à partir do Checkout#order_id. Como você disse, é um detalhe de implementação, mas um muito importante para não se testar.
Alternativamente você poderia criar uma classe responsável por fazer isso (e.g. OrderCreator.create(attrs)), e só verificar se a mensagem foi passada com um stub.
Muito bom Nando, ótima dica!
Tem um post antigo no blog da codeclimate também, com várias dicas de refactoring. Para quem quiser ler, segue o link:
http://blog.codeclimate.com...
Boas dicas!
Eu divido entre concern e service class (depois de muito tempo usando grails, é difícil acostumar quando outro framework não tem).
Não conhecia essa gem responders, parece ser bem útil, vou dar uma conferida depois.
Excelente artigo!
Só uma dúvida, e o Sandi Matz Rules (https://robots.thoughtbot.c...
Neste artigo comenta o uso de Facades para extrair também a lógica do controller. Neste artigo eu entendi que está aplicando um "service".
Abs!
Quero aplicar os conceitos da Sandi em um projeto, para ver como isso anda. Ao contrário do post da Thoughtbot, as regras são:
1. Classes de até 100 linhas
2. Métodos de até 5 linhas
3. Métodos com até 4 parâmetros
4. Actions do Rails podem ter 1 objeto de negócios e 1 de apresentação
Ela fez as correções em uma palestra (disponível no http://confreaks.tv). Esse último ponto é extremamente importante, pois facilita usar a abordagem que mostrei no artigo, com a possibilidade de se criar um objeto que será usado na apresentação (não que seja necessário neste caso em particular).
Perfeito Nando, essa palestra não tinha assistido ainda!
Estamos seguindo a regra a risca e confesso que achei bem "estranho" essa etapa do Facade, mais estou analisando e comparando se vale a pena de verdade deixar apenas 1 objeto de negócio/apresentação. Me parece muito mais adequado seguir o passo 4 atualizado (um de negócio - service, e outro de apresentação).
Novamente obrigado pelo excelente artigo!
Nando, antes de mais nada: parabéns pelo post, muito bom.
Usando essa definição de model com o include ActiveModel::Model como ficaria o resto da implementação da persistencia? Digo, você usa o ActiveRecord como repository? Como fica essa implementação?
boa =)
Show de bola. sei que não está dentro do conteúdo do post, mas que ferramenta vc usa para fazer mockups? igual a esse do checkout.