Aprenda / Ruby on Rails

IntermediárioAula 12 de 2016 min0% da trilha

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

app/controllers/pedidos_controller.rb
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
end

Os 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

app/controllers/sessoes_controller.rb
class SessoesController < ApplicationController
  skip_before_action :authenticate_usuario!, only: [:create]   # logar não exige já estar logado
end

Strong params além do básico

permitindo array e atributo aninhado
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
end

itens_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.

fat controller disfarçado
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
end
a mesma action, magra
def index
  pedidos = Pedido.all
  pedidos = pedidos.where("created_at >= ?", params[:desde]) if params[:desde].present?
  render json: PedidoSerializer.new(pedidos).serializable_hash
end

A 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).

Quiz rápido

Num controller com before_action :autenticar depois before_action :set_pedido, o que acontece se autenticar renderizar uma resposta de erro (ex.: 401)?
O que realmente significa um 'controller magro' na prática?