The Wrong Way
The Rails Way
The RABL Way
Why JSON?
My intent is not
You can learn from my mistakes
Then feel like this guy:
Already avoiding the Wrong Way or using RABL?
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
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
Rails 2.something
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:
{
title : "Just a test post.",
body : "Here's a bit of text for the "body"."
}
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\"."
}
Issues with this approach
- Building strings directly is error prone.
- The contoller is overloaded.
- Rails solves this problem already.
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"
}
Behind the scenes
render :json => @post.as_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
C'mon, Rails...
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
What about comments?
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!
# 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..."}
]
}
What about doing weird stuff?
# 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."
}
]
Other Options
ActiveModel::Serializers
Jbuilder
Thanks to:
- Rob Sullivan & Michael Gorsuch
- Jesse Harlin
- The Exchange
- Github