Arquitetura de controllers de produção
before_action e strong params fazem mais do que economizar linha — usados com disciplina, são o que separa um controller magro de um fat controller disfarçado.
A lição de controllers e rotas mostrou before_action :set_produto, only: [...] economizando uma linha repetida. Isso é só a superfície: before_action em cadeia, skip_before_action e strong params com estrutura aninhada resolvem problemas reais de organização — e entender exatamente como funcionam evita um controller que parece magro mas esconde regra de negócio dentro de si.
before_action em cadeia — a ordem importa, e interromper é normal
class PedidosController < ApplicationController
before_action :authenticate_usuario!
before_action :set_pedido, only: [:show, :update, :destroy]
before_action :verificar_editavel, only: [:update]
def update
if @pedido.update(pedido_params)
render json: @pedido
else
render json: { errors: @pedido.errors.full_messages }, status: :unprocessable_entity
end
end
private
def set_pedido
@pedido = Pedido.find(params[:id])
end
def verificar_editavel
return if @pedido.pendente?
render json: { error: "Pedido já finalizado não pode ser alterado" }, status: :unprocessable_entity
end
endOs três before_action rodam na ordem declarada: primeiro autentica, depois busca o pedido, depois checa se ele ainda pode ser editado. Se qualquer um deles chamar render (ou head), a cadeia para ali — os before_action seguintes e a action nunca rodam. É assim que verificar_editavel impede update de executar sem precisar de um if dentro dela.
skip_before_action — a exceção declarada, não um if a mais
class SessoesController < ApplicationController
skip_before_action :authenticate_usuario!, only: [:create] # logar não exige já estar logado
endStrong params além do básico
def pedido_params
params.require(:pedido).permit(:total, itens_attributes: [:produto_id, :quantidade])
end
def produto_params
params.require(:produto).permit(:nome, :preco, tags: []) # tags: [] permite um array de valores simples
enditens_attributes: [...] permite uma lista de hashes aninhados (o formato que accepts_nested_attributes_for espera); tags: [] permite um array de valores simples. Em ambos os casos, qualquer chave fora do que foi listado continua sendo descartada silenciosamente — a regra da lição de scaffold não muda, só fica mais expressiva.
O que "controller magro" significa de fato
Controller magro não é sinônimo de "controller pequeno" contado em linhas — é um controller que faz só três coisas: extrai parâmetro, delega a regra de negócio a outra camada (model ou service), devolve a resposta. Se tem if de regra de negócio, cálculo de valor ou filtro manual em Ruby dentro de uma action, é sinal de lógica vazando pro lugar errado.
def index
pedidos = Pedido.all
if params[:desde]
pedidos = pedidos.select { |p| p.created_at >= Date.parse(params[:desde]) } # filtra em Ruby, depois de carregar tudo
end
total_geral = pedidos.sum { |p| p.total } # soma em Ruby, não no banco
render json: { pedidos: pedidos, total: total_geral } # formato de resposta na mão
enddef index
pedidos = Pedido.all
pedidos = pedidos.where("created_at >= ?", params[:desde]) if params[:desde].present?
render json: PedidoSerializer.new(pedidos).serializable_hash
endA versão magra filtra no banco (uma query, não um select depois de carregar tudo), delega o formato de resposta ao serializer, e não calcula nada em Ruby que o SQL já resolve melhor. O controller continua sabendo só de HTTP — quem sabe a regra é o model e o serializer.
Exercício
Uma action finalizar hoje calcula o total do pedido, decrementa o estoque de cada item e envia o e-mail de confirmação, tudo dentro do controller. Reescreva a action delegando isso a um service (reaproveite a ideia de FinalizarPedido da lição de service objects).