Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Rails para APIs
Search
Nando Vieira
May 18, 2013
Programming
1.5k
19
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Rails para APIs
Veja como o Rails pode ser uma alternativa viável e muito melhor para a criação de APIs.
Nando Vieira
May 18, 2013
More Decks by Nando Vieira
See All by Nando Vieira
Permanecendo Relevante
fnando
9
1.5k
O que mudou no Rails 5
fnando
3
590
Porque usar PostgreSQL (Se você ainda não o faz)
fnando
25
3.1k
Criando aplicações web mais seguras
fnando
4
720
Criando aplicações mais fáceis de manter com Ruby on Rails
fnando
35
2.1k
Conhecendo Variants do Rails 4.1
fnando
11
610
JavaScript Funcional
fnando
36
1.8k
Segurança em Aplicações Web
fnando
31
3.1k
Segurança no Rails
fnando
20
980
Other Decks in Programming
See All in Programming
仕様駆動開発へのトライを機に チームに適合する手法を模索し続けている話
freee
PRO
0
670
私のClaude Code活用法 (個人開発編) - PHPerKaigi mini #4(2026/08/24)
panda_program
1
190
Deep dive into the select statement (GopherCon UK)
jespino
0
160
ドリフトを絶対に許さない(?)CDK運用 / CDK Ops with Zero Tolerance for Drifts (?)
akihisaikeda
1
250
Jindong: Introducing Declarative Haptics in Compose Multiplatform
l2hyunwoo
0
140
仕様駆動開発の消費期限
watany
20
9.2k
業務時間外もAIに働いてもらう話
colorful12
3
9.4k
S3 を使うアプリケーションをローカル完結で動かすことに全力を注いでみた / Running S3 Apps Offline
contour_gara
0
710
iOS開発×AI駆動開発 〜最近使って便利だったスキルの話〜
nogu66
0
120
AI時代に学ぶ 好きなルール 嫌いなルール Linter編
shorty5121
0
640
Go 1.27からのGODEBUG / Go 1.27 リリースパーティ #go127party
mazrean
0
270
レビュー履歴をAIに食わせて、 Compose移行を加速するs
shihochan
0
250
Featured
See All Featured
The Mindset for Success: Future Career Progression
greggifford
PRO
0
480
Visualizing Your Data: Incorporating Mongo into Loggly Infrastructure
mongodb
49
10k
CSS Pre-Processors: Stylus, Less & Sass
bermonpainter
360
30k
Put a Button on it: Removing Barriers to Going Fast.
kastner
60
4.5k
VelocityConf: Rendering Performance Case Studies
addyosmani
331
25k
コードの90%をAIが書く世界で何が待っているのか / What awaits us in a world where 90% of the code is written by AI
rkaga
63
45k
The Psychology of Web Performance [Beyond Tellerrand 2023]
tammyeverts
49
3.5k
The Straight Up "How To Draw Better" Workshop
denniskardys
239
140k
Designing for humans not robots
tammielis
254
26k
Fashionably flexible responsive web design (full day workshop)
malarkey
408
67k
Un-Boring Meetings
codingconduct
0
400
Making Projects Easy
brettharned
120
6.7k
Transcript
RAILS PARA APIS NANDO VIEIRA @fnando
http://nandovieira.com.br
http://hellobits.com
http://howtocode.com.br howto.
http://codeplane.com.br
None
O Rails mudou o modo como criamos aplicações web.
Não precisamos nos preocupar mais como será a organização dos
arquivos.
Nem como definir as rotas que nosso app vai reconhecer.
Mas isso tem um custo que para APIs pode não
ser muito bom.
APIs tem que ser rápidas.
Você tem outras alternativas se quiser continuar com Ruby, como
Sinatra.
No Sinatra, você volta ao problema de ter que fazer
tudo o que o Rails já fazia por você.
Rails para API = Rails - Web app stuff
Instalando
$ gem install rails --pre
$ rails new codeplane -TJ -d postgresql
Dependências
Remova tudo o que você não precisa do Gemfile.
ruby "2.0.0" source "https://rubygems.org" gem "rails", "4.0.0.rc1" gem "pg" gem
"rack-cache", require: "rack/cache" gem "dalli"
Middlewares
O Rails possui uma série de middlewares que não são
necessários para APIs.
# config/initializers/middleware.rb Rails.configuration.middleware.tap do |middleware| middleware.delete ActionDispatch::Cookies middleware.delete ActionDispatch::Flash middleware.delete
ActionDispatch::Session::CookieStore middleware.delete ActionDispatch::Static middleware.delete Rack::MethodOverride end
Recebendo parâmetros
Desabilite o CSRF token.
# app/controllers/application_controller.rb class ApplicationController < ActionController::Base # Prevent CSRF attacks
by raising an exception. # For APIs, you may want to use :null_session instead. # protect_from_forgery with: :exception end
No Rails, estamos acostumados a receber hashes como POST data.
User.new(params[:user])
{ name: "John Doe", email: "
[email protected]
", password: "[FILTERED]", password_confirmation: "[FILTERED]"
}
Isso não é comum em outras linguagens/frameworks.
Receber os parâmetros na raíz pode ser um problema.
post "/:name", to: "application#index"
$ http POST localhost:3002/hello name="John Doe"
params[:name] #=> hello
request.request_parameters
# app/services/body_data.rb class BodyData def initialize(params) @params = params end
def only(*attrs) @params.reduce({}) do |buffer, (key, value)| buffer[key] = value if attrs.include?(key.to_sym) buffer end end end
# app/controllers/application_controller.rb class ApplicationController < ActionController::Base def body_data @body_data ||=
BodyData.new(request.request_parameters) end end
repo = Repo.new body_data.only(:name)
Representando dados
Existem inúmeras maneiras de representar os dados em uma API.
JSON JavaScript Object Notation
# app/controllers/repos_controller.rb class ReposController < ApplicationController respond_to :json def index
repos = RepoList.new(current_user).all respond_with repos end end
Crie uma camada de abstração dos dados que serão representados.
http://rubygems.org/gems/active_model_serializers
# app/models/repo.rb class Repo < ActiveRecord::Base def active_model_serializer RepoSerializer end
end
# app/serializers/repo_serializer.rb class RepoSerializer < ActiveModel::Serializer attributes :name, :created_at end
http localhost:3002/repos
{ "repos": [ { "created_at": "2013-05-18T13:48:11.006Z", "name": "codeplane" }, {
"created_at": "2013-05-18T13:48:17.109Z", "name": "howto" } ] }
Schemaless é bom, mas é ruim.
http://stateless.co/hal_specification.html
http://jsonapi.org
JSON com significado e Hypermedia permitem criar ferramentas automatizadas.
https://github.com/apotonick/roar
# app/controllers/repos_controller.rb class ReposController < ApplicationController include Roar::Rails::ControllerAdditions respond_to :json
def index respond_with Repo.all end end
# app/representers/repos_representer.rb module ReposRepresenter include Roar::Representer::JSON::HAL include Roar::Representer::Feature::Hypermedia collection :repos,
:extend => RepoRepresenter link :self do repos_url end def repos each end end
# app/representers/repo_representer.rb module RepoRepresenter include Roar::Representer::JSON::HAL include Roar::Representer::Feature::Hypermedia property :name
property :created_at link :self do repo_url(represented) end end
{ "_links": { "self": { "href": "http://127.0.0.1:3000/repos" } }, "repos":
[ { "_links": { "self": { "href": "http://127.0.0.1:3000/repos/1" } }, "created_at": "2013-05-18T13:48:11.006Z", "name": "codeplane" } ] }
O Rails possui suporte às convenções REST desde o Rails
2.
Mas isso não significa que seu app será Hypermedia.
Não implementa o método HTTP OPTIONS.
# config/initializers/action_dispatch.rb class ActionDispatch::Routing::Mapper module HttpHelpers def options(*args, &block) map_method(:options,
args, &block) end end end
http http://localhost:3000/
{ "_links": { "locales": { "href": "http://localhost:3000/help/locales" }, "self": {
"href": "http://localhost:3000/" }, "timezones": { "href": "http://localhost:3000/help/timezones" }, "users": { "href": "http://localhost:3000/users" } } }
http http://localhost:3000/help/locales
{ "_links": { "self": { "href": "http://localhost:3000/help/locales" } }, "locales":
[ { "code": "en", "name": "English" }, { "code": "pt-BR", "name": "Brazilian Portuguese" } ] }
http OPTIONS http://localhost:3000/users
HTTP/1.1 200 OK Allow: POST Cache-Control: max-age=0, private, must-revalidate Connection:
close Content-Encoding: gzip Content-Type: text/html ETag: "bfdba25abe4e2efd6ba82155b8aa0719" Server: thin 1.5.1 codename Straight Razor Vary: Accept-Language,Accept-Encoding,Origin X-Request-Id: ca7031fd-5190-4d9c-87b5-fac42dff926a X-Runtime: 0.036035
# config/routes.rb Codeplane::Application.routes.draw do controller :users do post "/users", action:
:create options "/users", to: allow(:post), as: false end end
# config/initializers/action_dispatch.rb class ActionDispatch::Routing::Mapper module HttpHelpers def allow(*verbs) AllowedMethods.new(*verbs) end
end end
# config/middleware/allowed_methods.rb class AllowedMethods attr_reader :verbs def initialize(*verbs) @verbs =
verbs end def call(env) [ 200, { "Content-Type" => "text/html", "Allow" => verbs.map(&:upcase).join(",") }, [] ] end end
http POST http://localhost:3000/users
{ "errors": { "email": [ "is invalid" ], "name": [
"can't be blank" ], "password": [ "can't be blank" ], "username": [ "is invalid" ] } }
http POST http://localhost:3000/users \ Accept-Language:pt-BR
{ "errors": { "email": [ "é inválido" ], "name": [
"não pode ficar em branco" ], "password": [ "não pode ficar em branco" ], "username": [ "não é válido" ] } }
class Locale attr_reader :app, :i18n def initialize(app, i18n = I18n)
@app = app @i18n = i18n end def call(env) i18n.locale = env["HTTP_ACCEPT_LANGUAGE"] app.call(env) end end
Autenticação
Aqui você também possui diversas alternativas.
Autenticação por query string, header, OAuth, Basic Auth, e muito
mais.
Comece simples e mude a autenticação no futuro se for
necessário.
O Rails possui seu próprio mecanismo de autenticação por token.
authenticate_or_request_with_http_token(realm, &block)
# app/controllers/application_controller.rb class ApplicationController < ActionController::Base before_action :require_authentication attr_reader :current_user
def require_authentication authenticate_or_request_with_http_token do |token, options| @current_user = Authorizer.authorize(token) end end end
http localhost:3002/repos Authorization:"Token token=abc"
Exija HTTPS.
Codeplane::Application.configure do config.force_ssl = true end
http://www.cheapssls.com
Veracidade
Muitas APIs precisam garantir a veracidade das informações que estão
sendo enviadas.
Assinatura
secret + timestamp + url + data
require "openssl" # Request time timestamp = Time.now # Requested
url url = "https://codeplane.com/repos" # API secret secret = "MYSUPERSECRET" # POST data options = {name: "myrepo"} # Hashing algorithm digest = OpenSSL::Digest::Digest.new("sha512")
components = [url, secret, timestamp] components += options .keys .sort
.reduce(components) do |buffer, key| buffer.concat [key, options[key]] end
signature = OpenSSL::HMAC.hexdigest( digest, secret, components.join("") )
https://gist.github.com/fnando/5885956b831ea2c9b31f
Caching
Além das técnicas de caching usuais da aplicação, você pode
implementar HTTP caching.
O Rack::Cache permite usar HTTP caching com headers de tempo
de expiração (Expires, Cache- Control) ou validação (Etag, Last- Modified). https://github.com/rtomayko/rack-cache
# config/initializers/middleware.rb Rails.configuration.middleware.tap do |middleware| # ... middleware.use ::Rack::Cache, :verbose
=> true, :metastore => "memcached://localhost:11211/meta", :entitystore => "memcached://localhost:11211/body" end
http http://localhost:3000/
HTTP/1.1 200 OK Cache-Control: max-age=0, private, must-revalidate Connection: close Content-Encoding:
gzip Content-Type: application/json; charset=utf-8 ETag: "1358b2b2117fd019f60598f7a5514da2" Server: thin 1.5.1 codename Straight Razor Vary: Accept-Language,Accept-Encoding,Origin X-Request-Id: 4e7b55af-236c-4fcd-ae09-18d88e9019c0 X-Runtime: 0.071640
GZIP
Sirva compressão GZIP para quem entende.
http GET http://localhost:3000/ \ Accept-Encoding:none
http GET http://localhost:3000/ \ Accept-Encoding:gzip
HTTP/1.1 200 OK Cache-Control: max-age=0, private, must-revalidate Connection: close Content-Encoding:
gzip Content-Type: application/json; charset=utf-8 ETag: "1358b2b2117fd019f60598f7a5514da2" Server: thin 1.5.1 codename Straight Razor Vary: Accept-Language,Accept-Encoding,Origin X-Request-Id: 4e7b55af-236c-4fcd-ae09-18d88e9019c0 X-Runtime: 0.071640
Finalizando...
O Rails é uma excelente alternativa para APIs.
Você tira todas as decisões que não são importantes da
frente.
Obrigado!