Introducing Blueprinter
A new way to format and serialize APIs.
Blueprinter is a declarative and fast Ruby object to JSON serializer, an alternative to solutions like Active Model Serializers.
Why build another serializer?
When we began writing Blueprinter, existing options at the time of writing were few, most suffered from poor performance and hard to read code.
Procore has a very large API, with hundreds of endpoints representing hundreds more data structures. We needed a fast, simple option that we could use across our gamut of different needs. We noticed an opportunity to solve our needs as well as something we could offer that could fill a need in the Rails community.
Ok, so what does blueprinter offer?
The main difference that Blueprinter offers is the concept of views. A view in this context is a way to define multiple representations of an object for different uses. For example, in an API one often desires a different set of data coming back for an object via an index
action vs a show
action, and a very limited set of information when included as an association in other objects' endpoints. With most serializers, you would have to define a new serializer class for each different representation. Multiply this by about 500 models and you've got a real mess.
On top of this, we've committed to keeping Blueprinter clean, readable, easy to modify, simple, and extensible.
Using Blueprinter
So let’s talk about how you can actually implement Blueprinter on a rails project. In this tutorial, we will use Blueprinter for a Todo app built on Ruby on Rails.The tutorial will walk you through using Blueprinter to generate JSON for index
and show
endpoints.
Given you already have a Ruby on Rails app newly generated, you should now create the Todo
model:
class Todo < ApplicationRecord
validates :name, :description, presence: true
end
It has a corresponding migration that looks like this:
# create_todos
def change
create_table :todos do |t|
t.string :name
t.text :description
t.datetime :completed_at
t.datetime :due_at
t.timestamps
end
end
Don't forget to run rake db:migrate
.
Next, let's seed the database so that we have some samples to work with:
#db/seeds.rb
10.times do |num|
Todo.create(name: "todo#{num}",
description: "The #{num} thing(s) we need to do",
due_at: Time.now + num.days)
end
Finally run rake db:seed
to seed the database.
The API Endpoints with Blueprinter
Add Blueprinter to your gemfile.
# Gemfile
gem 'blueprinter'
Now run bundle install
from your terminal to install the gem into your project.
We would want our index
route to respond to GET /api/todos
with a JSON containing an array of todos with limited fields:
[
{
"id":1,
"completed_at":null,
"due_at":"2018-03-01 23:09:53 UTC",
"name":"todo0"
},
{
"id":2,
"completed_at":null,
"due_at":"2018-03-02 23:09:53 UTC",
"name":"todo1"
},
...
]
As for our show
route, we want it to respond to GET /api/todos/:id
with a json containing a single todo object with extended fields:
{
"id":1,
"completed_at":null,
"created_at":"2018-03-01 23:09:53 UTC",
"description":"The 0 thing(s) we need to do",
"due_at":"2018-03-01 23:09:53 UTC",
"name":"todo0",
"updated_at":"2018-03-01 23:09:53 UTC"
}
As you can see, the index
route and the show
route responds with the same type of object, Todo
, however with different representations. The show
route returning more fields (extended fields) than index
. Blueprinter
uses a concept called a view
to set different representations for the same object. Let's jump into this by defining a TodoBlueprint
class. Create a new file at app/blueprints/todo_blueprint.rb
. The content should look like:
# app/blueprints/todo_blueprint.rb
class TodoBlueprint < Blueprinter::Base
identifier :id
view :normal do
fields :name, :due_at, :completed_at
end
view :extended do
include_view :normal
fields :description, :created_at, :updated_at
end
end
As you can see, the :normal
view only contains three fields, where as the :extended
view will include the fields from :normal
as well as three additional fields. Additionally, all of the views will contain the :id
field of the todo object.
Next we will create the TodosController
with the index
and show
actions. The index
actions will serialize a collection of Todo
objects with Blueprinter
by passing to TodoBlueprint.render
the objects, and the :normal
view. While the show
action will render a single object using the :extended
view.
# app/controllers/api/todos_controller.rb
module Api
class TodosController < ApplicationController
def index
todos = TodoBlueprint.render Todo.all, view: :normal
render json: todos
end
def show
todo = TodoBlueprint.render Todo.find(params[:id]), view: :extended
render json: todo
end
end
end
Finally, let's add todos to the route:
# config/routes.rb
Rails.application.routes.draw do
namespace :api do
resources :todos, only: [:index, :show]
end
end
Oj Support (Optional Step)
If you want Blueprinter to use OJ to generate JSON instead of the default JSON gem, you can do that.
In your Gemfile, add Oj and run bundle install
:
# Gemfile
gem 'oj'
Finally, create an initializer file and add the following:
# config/initializers/blueprinter.rb
require 'oj'
Blueprinter.configure do |config|
config.generator = Oj
end
Try it out
Ensure your server is started with rails s
.
Navigate to localhost:3000/api/todos
and you should now see the JSON response with a list of todos.
Next, navigate to localhost:3000/api/todos/1
and you should see the JSON response for a single object with the extended fields.
We welcome questions, discussions, and contributions on Blueprinter at the Github repo.