Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Fast, testable and sane APIs - Ancient City Ruby 2014

Fast, testable and sane APIs - Ancient City Ruby 2014

By now, we've all written JSON APIs in Rails. But how do you write fast, testable and sane APIs? I'll guide you through the trials of designing and building awesome, scalable APIs. We'll cover Rails-API, ActiveModel::Serializers, and all the associated goodness that our ecosystem has to offer.

I'll speak on the approaches to authentication, how to ensure we remain good REST/HTTP citizens and maybe if I have time I'll share some of my top secret beard grooming tips!

Ben Lovell

April 03, 2014
Tweet

More Decks by Ben Lovell

Other Decks in Programming

Transcript

  1. { "links": { "posts.author": { "href": "http://example.com/people/{posts.author}", "type": "people" },

    "posts.comments": { "href": "http://example.com/comments/{posts.comments}", "type": "comments" } }, "posts": [{ "id": "1", "title": "Rails is Omakase", "links": { "author": "9", "comments": [ "5", "12", "17", "20" ] } }] }
  2. { "posts": [{ "id": "1", "title": "Rails is Omakase", "links":

    { "author": "9", "comments": [ "5", "12", "17", "20" ] } }] }
  3. { "posts": [{ "id": 1 // a post document }]

    } { }, { }] } Singular Resource Resource Collection
  4. { }] } { "posts": [{ "id": 1 // a

    post document }, { "id": 2 // a post document }] } Singular Resource Resource Collection
  5. { "posts": [{ "id": "1", "title": "Rails is Omakase", "links":

    { "comments": [ "5", "12", "17", "20" ] } }] } To-Many Relationships
  6. { "posts": [{ "id": "1", "title": "Rails is Omakase", "links":

    { "comments": { "href": "http://example.com/comments/5,12,17", "ids": [ "5", "12", “17" ], "type": "comments" } } }] } To-Many Relationships (link style)
  7. { "links": { "posts.comments": "http://example.com/posts/{posts.id}/comments" }, "posts": [{ "id": "1",

    "title": "Rails is Omakase" }, { "id": "2", "title": "The Parley Letter" }] } Shorthand (link style) RFC6570 URI Template
  8. { "links": { "posts.author": { "href": "http://example.com/people/{posts.author}", "type": "people" },

    "posts.comments": { "href": "http://example.com/comments/{posts.comments}", "type": "comments" } }, "posts": [{ "id": "1", "title": "Rails is Omakase", "links": { "author": "9", "comments": [ “1" ] }}], "linked": { "authors": [{ "id": "9", "name": "@dhh" }], "comments": [{ "id": "1", "body": "Mmmmmakase" }] } }
  9. wat

  10. YO DAWG! I heard you like rails so I took

    some rails out of your rails so you could rails (a little bit faster than usual)
  11. use Rack::Sendfile use ActionDispatch::Static use Rack::Lock use #<ActiveSupport::Cache::Strategy::LocalCache::Middleware> use Rack::Runtime

    use Rack::MethodOverride use ActionDispatch::RequestId use Rails::Rack::Logger use ActionDispatch::ShowExceptions use ActionDispatch::DebugExceptions use ActionDispatch::RemoteIp use ActionDispatch::Reloader use ActionDispatch::Callbacks use ActiveRecord::Migration::CheckPending use ActiveRecord::ConnectionAdapters::ConnectionManagement use ActiveRecord::QueryCache use ActionDispatch::Cookies use ActionDispatch::Session::CookieStore use ActionDispatch::Flash use ActionDispatch::ParamsParser use Rack::Head use Rack::ConditionalGet use Rack::ETag run KitchenSinkFullOfKitchenSinks::Application.routes
  12. use Rack::Sendfile use ActionDispatch::Static use Rack::Lock use #<ActiveSupport::Cache::Strategy::LocalCache::Middleware> use Rack::Runtime

    use Rack::MethodOverride use ActionDispatch::RequestId use Rails::Rack::Logger use ActionDispatch::ShowExceptions use ActionDispatch::DebugExceptions use ActionDispatch::RemoteIp use ActionDispatch::Reloader use ActionDispatch::Callbacks use ActiveRecord::Migration::CheckPending use ActiveRecord::ConnectionAdapters::ConnectionManagement use ActiveRecord::QueryCache use ActionDispatch::Cookies use ActionDispatch::Session::CookieStore use ActionDispatch::Flash use ActionDispatch::ParamsParser use Rack::Head use Rack::ConditionalGet use Rack::ETag run KitchenSink::Application.routes
  13. irb(main):003:0> pp ActionController::API.ancestors - ActionController::Metal.ancestors [ ActionController::API, ActiveRecord::Railties::ControllerRuntime, ActionDispatch::Routing::RouteSet::MountedHelpers, ActionController::StrongParameters,

    ActionController::Instrumentation, ActionController::Rescue, ActiveSupport::Rescuable, ActionController::DataStreaming, ActionController::ForceSSL, AbstractController::Callbacks, ActiveSupport::Callbacks, ActionController::ConditionalGet, ActionController::Head, ActionController::Renderers::All, ActionController::Renderers, ActionController::Rendering, AbstractController::Rendering, AbstractController::ViewPaths, ActionController::Redirecting, ActionController::RackDelegation, ActiveSupport::Benchmarkable, AbstractController::Logger, ActionController::UrlFor, AbstractController::UrlFor, ActionDispatch::Routing::UrlFor, ActionDispatch::Routing::PolymorphicRoutes, ActionController::ModelNaming, ActionController::HideActions ]
  14. Jbuilder.encode do |json| json.content format_content(@message.content) json.(@message, :created_at, :updated_at) ! json.author

    do json.name @message.creator.name.familiar json.email_address @message.creator.email_address_with_name json.url url_for(@message.creator, format: :json) end ! if current_user.admin? json.visitors calculate_visitors(@message) end ! json.comments @message.comments, :content, :created_at ! json.attachments @message.attachments do |attachment| json.filename attachment.filename json.url url_for(attachment) end end
  15. Jbuilder json json ! json json json json end !

    if json end ! json ! json json json end end
  16. class PostSerializer < ActiveModel::Serializer attributes :id, :title, :body, :synopsis has_many

    :comments ! def comments object.comments.where(:author => scope) end ! def synopsis object.body.truncate(30) end end
  17. class PostSerializer < ActiveModel::Serializer attributes :id, :title, :body has_many :comments

    has_many :tags has_many :images belongs_to :author end WARNING!
  18. class PostsController < ApplicationController def show @post = Post.find(params[:id]) !

    if stale? @post respond_with @post end end end etag: Model#cache_key last_modified: Model#updated_at