wat.

The Wrong Way

The Rails Way

The RABL  Way

Why JSON?

json!
wat.

Anti-patterns

My intent is not

wat.

You can learn from my mistakes

Then feel like this guy:

wat.

Already avoiding the Wrong Way or using RABL?

wat.
json!

  rails new rabl-slides-demo
  

  rails g scaffold post title:string body:text
  

  class CreatePosts < ActiveRecord::Migration
    def change
      create_table :posts do |t|
        t.string :title
        t.text :body

        t.timestamps
      end
    end
  end
  

  class Post < ActiveRecord::Base
  end
  
wat.

And the only prescrition

is more JSON!

A naive approach


class PostsController < ApplicationController
  def show
    @post = Post.find params[:id]
    render :text => %[{
      title : "#{@post.title}",
      body  : "#{@post.body}"
     }]
  end
end
json!

Rails 2.something

wat.

It's ugly, but it works!

GET /posts/1
{
      title : "Just a test post.",
      body  : "Here's a bit of text for the body."
     }

Kinda...

For a while...

One day, a JSON
consumer said:

json!
{
      title : "Just a test post.",
      body  : "Here's a bit of text for the "body"."
     }
json!
json!

A slight improvement



    render :text => %[{
      title : "#{@post.title.gsub('"','\"')}",
      body  : "#{@post.body.gsub('"','\"')}"
     }]
{
      title : "Just a test post",
      body  : "Here's a bit of text for the \"body\"."
     }
wat.

This is a
losing battle.

Issues with this approach


  • Building strings directly is error prone.
  • The contoller is overloaded.
  • Rails solves this problem already.
json!

The Rails Way TM

wat.

Schrodinger's Action

Does it do anything?

class PostsController < ApplicationController
  def show
    @post = Post.find(params[:id])
  end
end
GET /posts/1.json
{
  "title":"Just a test",
  "body":"Here's a bit of text for the \"body\".",
  "created_at":"2013-10-03T05:02:56.901Z",
  "updated_at":"2013-10-03T05:02:56.901Z"
}
wat.

Rails "magic" at work

Behind the scenes


  render :json => @post.as_json
  
json!

as_json customization

  • :root
  • :except
  • :only
  • :methods
  • :include

Whitelisting


  def show
    @post = Post.find(params[:id])
    render :json => @post.as_json(:only => [:title, :body])
  end
  

Blacklisting


  def show
    @post = Post.find(params[:id])
    render :json => @post.as_json(:except => [:created_at, :updated_at])
  end
  

Issues with this approach


  • Building strings directly is error prone.
  • The contoller is overloaded.
  • Rails solves this problem already.

  def show
    @post = Post.find(params[:id])
    render :json => @post.api_json
  end
  

  class Post < ActiveRecord::Base
    def api_json
      as_json(:except => [:created_at, :updated_at])
    end
  end
  

Issues with this approach


  • Building strings directly is error prone.
  • The contoller is overloaded.
  • Rails solves this problem already.
  • The model is overloaded.
  • The view does not live in app/views

wat.

C'mon, Rails...

json!

The RABL Way


JSON is the view


  # Gemfile
  gem "rabl"
  

  # app/controllers/posts_controller.rb
  class PostsController < ApplicationController
    def show
      @post = Post.find(params[:id])
    end
  end
  

  # app/views/posts/show.json.rabl
  object @post
  attributes :title, :body
  

  {
    "title":"Just a test",
    "body":"Here's a bit of text for the \"body\"."
  }
  

Issues with this approach


  • Building strings directly is error prone.
  • The contoller is overloaded.
  • Rails solves this problem already.
  • The model is overloaded.
  • The view does not live in app/views
json!

What about comments?


json!

  rails g scaffold comment body:text username:text
  

  class Comment < ActiveRecord::Base
    belongs_to :post
  end
  

  class Post < ActiveRecord::Base
    has_many :comments
  end
  

  # app/views/posts/show.json.rabl
  object @post
  attributes :title, :body

  child :comments do
    attributes :body, :username
  end
  

  {
    "title":"Just a test",
    "body":"Here's a bit of text for the \"body\".",
    "comments" : [
      {"username" : "Jane D'oh", "body" : "This post is lame!"},
      {"username" : "Joe Blow", "body" : "Totally super lame..."}
    ]
  }
  

Let's refactor!

json!

  # app/views/comments/show.json.rabl
  object @comment
  attributes :body, :username
  

  # app/views/posts/show.json.rabl
  object @post
  attributes :title, :body

  child :comments do
    extends "comments/show"
  end
  

  {
    "title":"Just a test",
    "body":"Here's a bit of text for the \"body\".",
    "comments" : [
      {"username" : "Jane D'oh", "body" : "This post is lame!"},
      {"username" : "Joe Blow", "body" : "Totally super lame..."}
    ]
  }
  
json!

What about doing weird stuff?


json!

  # app/views/posts/show.json.rabl
  object @post
  attributes :title, :body

  node :random_100 do
    rand(100)
  end

  node :random_10 do
    rand(10)
  end
  

  {
    "title":"Just a test",
    "body":"Here's a bit of text for the \"body\".",
    "random_100" : 42,
    "random_10"  : 7
  }
  

What about caching?



  # app/views/posts/show.json.rabl
  object @post
  cache @post
  attributes :title, :body
  

What about collection listings?



  # app/views/posts/index.json.rabl
  object @posts
  cache @posts
  extends "posts/show"
  

  [
    {
      "title":"Just a test",
      "body":"Here's a bit of text for the \"body\"."
    },
    {
      "title":"One more post",
      "body":"Another useless post."
    }
  ]
  
json!

Other Options


  • ActiveModel::Serializers

  • Jbuilder

json!

Thanks to:


  • Rob Sullivan & Michael Gorsuch
  • Jesse Harlin
  • The Exchange
  • Github
json!

Thanks For Watching!


Jeremy Green
jeremy@octolabs.com

https://github.com/Octo-Labs/rabl-slides


http://www.octolabs.com/