Service objects
Quando uma ação envolve mais de um model, ela não pertence nem ao controller nem a um model só.
"Finalizar um pedido" não é só Pedido.update: baixa estoque, cria um registro de pagamento, manda e-mail de confirmação. Colocar tudo isso direto no controller (um "fat controller") mistura HTTP com regra de negócio; colocar no model Pedido mistura persistência com orquestração de várias tabelas. Um service object é uma classe cuja única responsabilidade é essa ação.
class FinalizarPedido
def initialize(pedido)
@pedido = pedido
end
def call
return false unless @pedido.itens.any?
ActiveRecord::Base.transaction do
@pedido.update!(status: :finalizado)
@pedido.itens.each { |item| item.produto.decrement!(:estoque, item.quantidade) }
Pagamento.create!(pedido: @pedido, valor: @pedido.total)
end
PedidoMailer.confirmacao(@pedido).deliver_later
true
end
enddef finalizar
pedido = Pedido.find(params[:id])
if FinalizarPedido.new(pedido).call
render json: pedido
else
render json: { error: "Pedido sem itens" }, status: :unprocessable_entity
end
endNote o ActiveRecord::Base.transaction: se Pagamento.create! falhar depois do estoque já ter sido baixado, a transação desfaz tudo — sem isso, um erro no meio do processo deixaria o banco num estado inconsistente (estoque baixado, mas sem pagamento registrado).
Quando NÃO criar um service
Uma action que só salva um registro (Produto.create(params)) não precisa de service — isso é exatamente pra que o model e o controller já servem. Service object resolve orquestração entre múltiplas entidades, não é uma camada obrigatória em toda action.
Exercício
Um CancelarPedido precisa: reverter o status pra "cancelado", devolver o estoque de cada item, e só funcionar se o pedido ainda não tiver sido entregue. Esboce a classe.