Este é mais um post da série APIs RESTful com Grape, o terceiro da série. No primeiro post, mostramos como montar seu ambiente e como estruturar seu projeto. Além disso, montamos uma API simples cujo o objetivo é prover dados sobre cervejas. Até aqui, temos apenas uma API funcional e provendo uma lista de tipos de cervejas. Adiante, já no segundo post, vimos como usar o gem grape-entity para evitar expor nossos Models através da API.
Continuando essa série, vamos agora implementar os métodos POST, PUT, DELETE e GET(id) para criar, atualizar, remover e obter uma cerveja e um tipo de cerveja específico. Por debaixo dos panos, também estaremos usando o DataMapper para realizar a persistência dos dados. Relembrando, pela terceira vez, este projeto está no Github. Sugiro que você baixe-o e utilize esta série de posts apenas como guia para entender o código fonte que está no projeto.
DataMapper
Conhece o DataMapper? Trata-se simplesmente de um framework para persistência de dados. Só isso mesmo. Caso não goste de frameworks deste tipo, você pode fazer tudo na unha, é problema seu. Ou, caso não goste do DataMapper, você também pode usar o ActiveRecord. Por fim, caso você não queira usar bancos de dados relacionais, você pode dar uma olhada no MongoMapper. Só aviso que nosso objetivo aqui não é detalhar como funcionam estas ferramentas. Talvez em um post futuro, mas não nesse! Neste caso, vamos fazer apenas o básico do básico para colocar o DataMapper funcionando neste projeto.
Instalando
Rápido e caceteiro! Digite gem install datamapper para instalar o DataMapper globalmente. Em seguida, atualize seu arquivo Gemfile, adicionando a linha gem ‘data_mapper’. Finalizando, execute bundle install para instalar os gems necessários para o projeto. Fim!
Alterando os Models
Agora, precisamos alterar nossos models para que eles tenham persistência de dados. Mas não se assuste, é coisa bem simples, como você pode ver na listagem de código abaixo.
require 'data_mapper' module Models # Cervejas. class Cerveja include DataMapper::Resource property :id, Serial property :nome, String belongs_to :tipo end # Tipos de Cervejas class Tipo include DataMapper::Resource property :id, Serial property :nome, String has n, :cervejas end end
O que nós fizemos? Primeiro, precisamos fazer um “require” do DataMapper logo no início do arquivo models.rb. Segundo, precisamos alterar nossas classes para incluir o DataMapper::Resource e definir as propriedades, que virarão colunas nas tabelas que representarão cada classe. Para definir as propriedades, usamos o método property, que recebe dois parâmetros: um Symbol que representa o nome da propriedade e o tipo da propriedade. Observe que neste exemplo em específico, ainda precisamos fazer um relacionamento de “um-para-muitos” entre tipo e cerveja. Na classe Cerveja, informamos isto através da linha belongs_to :tipo, enquanto na classe Tipo, fizemos has n, :cervejas. Simplificando: um Tipo tem muitas cervejas e uma Cerveja tem apenas um Tipo.
Finalizando
Para colocar o DataMapper rodando, agora só precisamos definir o Adapter de banco de dados a ser usado e avisar a ele para criar automaticamente toda a estrutura do banco de dados. Como fazemos isso? Do modo mais simples possível: alterando nosso arquivo config.ru. Mas, antes disso, você precisará instalar e configurar um banco de dados de sua preferência. Para este artigo, usarei o MariaDB, herdeiro do MySQL. Por que? Porque sim e pronto.
Comece instalando o gem ‘dm-mysql-adapter‘ e não se esqueça de incluí-lo também no Gemfile, viu? Agora altere seu arquivo config.ru para ficar parecido com a listagem abaixo. Observe que adicionamos apenas quatro linhas. Primeiro, o require para o DataMapper. Segundo, informamos o banco de dados através do método setup, em seguida, chamamos o método auto_migrate! para criar a estrutura das tabelas automaticamente e finalizamos chamando finalize! Pronto! Cabô essa seção, ufa! Muito chata!
$:.unshift "./app" require 'rack/cors' require 'rubygems' require 'grape' require 'rack' require 'grape-entity' require 'data_mapper' require 'api/base.rb' DataMapper.setup(:default, 'mysql://seulogin:suasenha@localhost/bebumapi') DataMapper::auto_migrate! DataMapper::finalize use Rack::Cors do allow do origins '*' resource '*', headers: :any, methods: [:get, :post, :put, :delete, :options, :patch] end end run API::Base
API de Tipos e Cervejas
Agora, que tal fecharmos toda a API de Tipos? Vamos lá. Vamos implementar mais 3 métodos nela. O primeiro método será responsável por obter um Tipo de cerveja a partir de um ID e está definido na linha que tem get ‘:id’ do. Observe que a String ‘:id’ informa que teremos uma rota /tipos/algoaqui. Ou seja, sempre que alguém tentar acessar http://localhost:9292/tipos/*, cairá neste método. E como podemos obter esse valor :id? Simples, usando o Hash chamado params. Simples demais! Agora só chamamos o DataMapper pedindo o Model que representa esse recurso.
Vamos ver o método POST? Observe que não passamos uma String igual a como fizemos com o get. Exatamente porque não precisa, já que queremos uma rota POST na URL http://localhost:9292/tipos. Quando alguém fizer um post nessa rota, também deve passar algumas coisas como parâmetro. Neste caso, o nome do Tipo da cerveja. A partir daí, fica tudo mais fácil! Só criamos um objeto Models::Tipo e mandamos o DataMapper salvar. De lambuja, retornamos a entidade criada.
module API module V1 class Tipos < Grape::API resources :tipos do get do present Models::Tipo.all, :with => Entities::Tipo end get ':id' do present Models::Tipo.get!(params[:id]), :with => Entities::Tipo end post do tipo = Models::Tipo.new tipo.nome = params[:nome] tipo.save status 201 header 'Location', "/tipos/#{tipo.id}" present tipo, :with => Entities::Tipo end put ':id' do tipo = Models::Tipo.get params[:id] if tipo.nil? status 201 header 'Location', "/tipos/#{tipo.id}" tipo = Models::Tipo.new else status 200 end tipo.nome = params[:nome] tipo.save present tipo, :with => Entities::Tipo end delete ':id' do status 204 Models::Tipo.get!(params[:id]).destroy! end end end end end
Mas, opa! Lembra que eu falei que iríamos fazer uma API seguindo os princípios REST? Então, é prudente que a gente também retorne o cabeçalho Location informando a URI do recurso que acabamos de criar. E o Grape nos ajuda substancialmente para fazer isso! Basta usar header ‘Location’, “/tipos/#{tipo.id}”. De brinde, embora não seja necessário, pois o Grape já faz isso em todo método POST, informamos que queremos retornar um status 201 CREATED na linha status 201. Diga se não é lindo isso!
E o DELETE? Moleza, companheiro! Basta criar o bloco DELETE parecendo com o GET anterior e remover a entidade. Ah! Só não se esqueça de definir o status como 204 NO CONTENT, viu? Vamos finalizar com o PUT? Ele é só um pouco mais chato. Mas nada demais. Lembre que o PUT deve verificar se o ID informado se refere a um recurso existente. Caso não se refira, devemos criar esse recurso.
No código, a única diferença é que checamos se o objeto é nil. Caso seja, mudamos o status para 201 CREATED e também retornamos o cabeçalho Location. Caso contrário, botamos o status para 200 OK. O resto do código é similar ao que fizemos para o método POST.
E como fica a API de Cerveja? Rapaz, idêntica a essa. Claro, temos que tratar algumas coisas específicas, como o relacionamento entre Cerveja e Tipo. Mas não é nada de outro mundo, como você pode ver na listagem abaixo e que eu não vou explicar porque você é inteligente e já entendeu tudo! 😛
Pronto! Cabô mais um artigo! Quer saber sobre o que escreverei no próximo? Vamos ver como tratar erros genéricos, como, por exemplo, quando um recurso não existe ou quando o usuário da API esqueceu de enviar um parâmetro. Não perdaum!