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

Fast, testable and sane JSON-APIs with Rails-API et al

Fast, testable and sane JSON-APIs with Rails-API et al

This talk was given at Brighton Ruby Conference 2014.

Ben Lovell

July 21, 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. $ curl -i https://api.example.com/user HTTP/1.1 200 OK Cache-Control: private, max-age=60

    ETag: "644b5b0155e6404a9cc4bd9d8b1ae730" Last-Modified: Thu, 02 Feb 2014 15:31:30 GMT Status: 200 OK
  19. $ curl -i https://api.example.com/user -H ‘If-None-Match: ”{Etag}”’ HTTP/1.1 304 Not

    Modified Cache-Control: private, max-age=60 ETag: "644b5b0155e6404a9cc4bd9d8b1ae730" Last-Modified: Thu, 02 Feb 2014 15:31:30 GMT Status: 304 Not Modified
  20. 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