Projeto guiado: API REST completa
Um recurso Pedido do scaffold ao endpoint autenticado, autorizado e serializado — as 9 lições juntas.
Última peça: um recurso Pedido inteiro, do zero ao endpoint pronto pra produção, encadeando tudo que foi visto — scaffold, model com validação, controller REST, serializer, service object, Devise e CanCanCan.
1. Scaffold
rails generate scaffold Pedido usuario:references status:string total:decimal --api
rails db:migrate2. Model — validação e enum de status
app/models/pedido.rb
class Pedido < ApplicationRecord
belongs_to :usuario
has_many :itens
enum status: { pendente: 0, finalizado: 1, cancelado: 2 }
validates :total, numericality: { greater_than: 0 }
def entregue?
status == "finalizado"
end
end3. Ability — quem pode fazer o quê
app/models/ability.rb
class Ability
include CanCan::Ability
def initialize(usuario)
return unless usuario
if usuario.admin?
can :manage, :all
else
can [:read, :create], Pedido
can [:update], Pedido, usuario_id: usuario.id
cannot :destroy, Pedido
end
end
end4. Service — a ação que orquestra mais de uma tabela
app/services/finalizar_pedido.rb
class FinalizarPedido
def initialize(pedido)
@pedido = pedido
end
def call
return false unless @pedido.itens.any?
ActiveRecord::Base.transaction do
@pedido.finalizado!
@pedido.itens.each { |item| item.produto.decrement!(:estoque, item.quantidade) }
end
true
end
end5. Serializer — o formato de resposta
app/serializers/pedido_serializer.rb
class PedidoSerializer
include JSONAPI::Serializer
attributes :status, :total
belongs_to :usuario
end6. Controller — a única peça que conhece HTTP
app/controllers/pedidos_controller.rb
class PedidosController < ApplicationController
before_action :authenticate_usuario! # Devise: exige token válido
load_and_authorize_resource # CanCanCan: aplica a Ability
def index
render json: PedidoSerializer.new(current_usuario.pedidos).serializable_hash
end
def show
render json: PedidoSerializer.new(@pedido).serializable_hash
end
def create
pedido = current_usuario.pedidos.new(pedido_params)
if pedido.save
render json: PedidoSerializer.new(pedido).serializable_hash, status: :created
else
render json: { errors: pedido.errors.full_messages }, status: :unprocessable_entity
end
end
def finalizar
if FinalizarPedido.new(@pedido).call
render json: PedidoSerializer.new(@pedido).serializable_hash
else
render json: { error: "Pedido sem itens" }, status: :unprocessable_entity
end
end
private
def pedido_params
params.require(:pedido).permit(:total)
end
endconfig/routes.rb
Rails.application.routes.draw do
resources :pedidos, except: [:new, :edit, :destroy] do
member { post :finalizar } # POST /pedidos/:id/finalizar — rota extra, fora do REST padrão
end
endRepare a ordem das camadas dentro de PedidosController: authenticate_usuario! (quem é você) roda antes de load_and_authorize_resource (o que você pode fazer), que roda antes de qualquer lógica de negócio. Autenticação, depois autorização, depois regra de negócio — nessa ordem, sempre.
Exercício
Um usuário comum tenta POST /pedidos/7/finalizar num pedido que não é dele. Em que camada exatamente essa tentativa é barrada, e com qual status HTTP?