« HE:labs
HE:labs

Cuidados com Observers e callbacks

Postado por Anézio Campos em 14/01/2013

Já foi divulgado que na versão 4 do Rails removerão o Observer e ele deverá ser utilizado como uma gem. Analizei em alguns projetos a utilização dessa classe e as vezes encontro situações onde acredito que ela pode estar prejudicando mais do que colaborando com o projeto.

A grosso modo a utilização de Observer nada mais é do que uma extração de código dos callbacks. Ou seja, é necessário também muito cuidado ao ser utilizado para não exagerar na lógica que é colocada nela, já que isso pode gerar comportamentos não desejados da classe, além de aumentar a complexidade nos testes onde a gente acaba tendo que mockar/implementar funcionalidades extras do que realmente está querendo ser testado.

Um exemplo dessa situação é o Welcome email que é enviado quando um usuário é cadastrado.

1 class UserObserver < ActiveRecord::Observer
2       def after_create(user)
3           NotificationsMailer.welcome_user(user.id).deliver
4       end
5   end

DRY utilizando Observer

A utilização de observer sem que ele seja utilizado em multiplas classes é desnecessária, você não está fazendo nada mais do que pegando o callback de uma classe e extraindo para outra classe. Não vejo o que se ganha com isso. Além de estar complicando o desenvolvedor, já que ele tem que abrir 2 arquivos para entender o que está sendo realizado.

Essa extração é produtiva no caso onde diversas classes acabam executando o mesmo callback como nesse exemplo:

1 class AuditObserver < ActiveModel::Observer
2       observe :account, :balance
3 
4       def after_update(record)
5           AuditTrail.new(record, "UPDATED")
6       end
7   end

Aqui o Observer está criando um registro de auditoria tanto no Account quanto no Balance. Já justifica a sua utilização.

Caos do callback

Este tipo de callback cria um comportamento padrão para toda criação de usuário que nem sempre pode ser desejada. Por exemplo, posso querer importar usuários e não querer que seja disparado o welcome email, e sim outro email customizado.

Além disso isso também cria mais complexidade nos testes, suponha que quero enviar um email na mudança de status do User, nos testes vou precisar criar o usuário e alterar seu status para disparar o email que quero testar, no entanto ao criar o usuario estarei enviando o Welcome email, algo que realmente não é necessario e terei de fazer um tratamento nos testes para evita-lo.

Este caso é de um simples email extra enviado, mas agora imagine um after_create executando 4 ou 5 tarefas o quanto isso vai implicar na implementação de diversos testes e também o quanto vai surgir de tratamentos que deverão ser realizados para executar ou não cada tarefa, isso pode se tornar um grande BAD SMELL.

Exemplo de solução

Uma solução que tem se mostrado muito claro, simples e quebra a complexidade dos models é extrair comportamentos de um determinado cenario para uma nova classe. Por exemplo:

 1 class UserSignup
 2     def initialize(params)
 3       @user = User.new(params)
 4     end
 5 
 6     def signup
 7       if @user.save
 8         NotificationsMailer.welcome_user(user.id).deliver
 9       end
10       @user
11     end
12   end
13 
14   class UserController < ApplicationController
15     def create
16       @user = UserSignup.new(params).signup
17 
18       if @user.errors.present?
19         render :new
20       else
21         redirect_to dashboard_path, notice: "User create successfully"
22       end
23     end
24   end

Dessa forma estou extraindo a situação específica da criação de usuários pelo formulário padrão do projeto para uma classe que terá seu comportamento bem específico deixando assim o código mais simples para se testar, tirando complexidade da classe User e de quebra ainda deixará de executar em diversos testes do projeto os callbacks da criação do User o que irá impactar positivamente na velocidade da execução dos testes.

Tags: rails

Compartilhe

Sabia que nosso blog agora está no Medium? Confira Aqui!