Rabl with Padrino

Oct 22, 2012
D2d66b4e429c04b16e74d65f44c92815

Have you ever wanted to create an API without much hassle? And with that in mind, do you think that using Rails for it is an overkill?

Well, I'm going to show you a quick and easy way to create a simple API using Padrino + Rabl.

Padrino is a Ruby web framework built on top of Sinatra that makes it Rails-like, while Rabl (Ruby API Builder Language) is a Ruby templating engine that can generate JSON, XML and other formats with a simple DSL.

We'll start by creating a padrino project

$ padrino g project tasks -d activerecord -b

This will generate a structure similar to Rails

tasks
  app
  config
  db
  models
  public

Notice that with -d flag we can choose what ORM to use and -b flag is for instructing bundler to install all dependencies.

Next, we will add the rabl gem in the Gemfile and run 'bundle install'.

gem 'rabl'

and then run

$ bundle install

All should be good at this point. Time to create models! we'll be creating two models: Task and User.

$ padrino g model user first_name:string

Which will generate the User activerecord model and migration

apply  orms/activerecord
create  models/user.rb
create  db/migrate/001_create_users.rb

And now the Task model

$ padrino g model task title:string description:string due_time:date user_id:integer

We run migrations this way

$ padrino rake ar:migrate

CreateUsers: migrated (0.0021s) 
CreateTasks: migrated (0.0024s)

Both models should be migrated by now. It's time to create associations at model level.

class User < ActiveRecord::Base
  validates_presence_of :first_name
  has_many :tasks
end

class Task < ActiveRecord::Base
  validates_presence_of :title, :user_id
  belongs_to :user
end

If we want to test the models we can do it in the Padrino console this way

$ padrino c
=> Loading development console (Padrino v.0.10.7)
=> Loading Application Tasks
irb(main):001:0> 

And typing

> User.create first_name: 'Fernando'
…
> Task.create user_id: 1, title: 'Write blog post', description: 'About rabl + padrino', due_date: 1.hour.from_now

We should now have a user with one task. And we are good to create a tasks controller.

$ padrino g controller tasks get:index get:show
  create  app/controllers/tasks.rb
  create  app/helpers/tasks_helper.rb
  create  app/views/tasks

This generated a tasks controller and a tasks_helper, we indicated that we want the controller to have index and show actions via get.

So, finally we will do some work on the API! Firstly we will create an endpoint to get all of the tasks with it's user name. To get to that we need to tell the app that when we get a request to the 'http://localhost:3000/tasks' meaning the index action for tasks we need to fetch the tasks and return them.

app/controllers/tasks.rb

get :index do
  @tasks = Task.all
  render 'tasks/index'
end

Here we are telling Padrino to render an index template inside of the tasks folder, but we haven't created it yet, so we might as well do it with rabl.

The cool thing about rabl is that it just works out of the box with Padrino.

app/views/tasks/index.rabl

 # app/views/tasks/index.rabl
 collection @tasks

 attributes :id, :title, :description
 child(:user) { attributes :name }

So, here we are telling rabl that we are passing a collection through the @tasks instance variable and that we want to present the attributes :id, :title, :description and the user :first_name through the association.

Great, we are ready to test our API. For that, we need to first run it like this:

$ padrino start
=> Padrino/0.10.7 has taken the stage development at http://0.0.0.0:3000
..

Open our browser to this address http://localhost:3000/tasks and we should get this response:

[{"task":{"id":1,"title":"Write blog post","description":"About rabl + padrino","user":{"first_name":"Fernando"}}}]

Nice! just one thing; there are apps/people that don't like including the name of the resource/model into the root of the json because it's really not necessary. That's no problem, we can do it before we load Padrino.

config/boot.rb

Padrino.before_load do
  Rabl.configure do |config|
    config.include_json_root = false
  end
end

If we go again to this address http://localhost:3000/tasks we should be getting...

[{"id":1,"title":"Write blog post","description":"about rabl","due_time":"2012-10-14T21:58:39-07:00","user":{"first_name":"Fernando"}},{"id":2,"title":"Pay bills","description":"Cable and gas","due_time":"2012-10-16T23:16:38-07:00","user":{"first_name":"John"}}]

Pretty easy huh? But what if we want to return if the task is overdue? easy…

app/views/tasks/index.rabl

collection @tasks

attributes :id, :title, :description
child(:user) { attributes :first_name }
node(:on_time) {|task| task.due_time > Time.now}

And this is the returned value

[{"id":1,"title":"Write blog post","description":"about rabl","due_time":"2012-10-14T21:58:39-07:00","on_time":false,"user":{"first_name":"Fernando"}},{"id":2,"title":"Pay bills","description":"Cable and gas","due_time":"2012-10-16T23:16:38-07:00","on_time":true,"user":{"first_name":"John"}}]

What node is doing is that it is creating a node in the response with the name we describe, the value of that node is defined on the block that the node receives.

What if you want to get a specific task? Simple:

app/controllers/tasks.rb

get :show, :with => :id do
  @task = Task.find(params[:id].to_i)

  render 'tasks/show'
end

We create a get action with the name show in the tasks controller that can receive a parameter named id, and we use its value to search the database for that record. Then we render the show template.

app/views/tasks/show.rabl

object @task

attributes :id, :title, :description

node :errors do |e|
  e.errors
end

We will explain later the node errors.

Now if we go in the browser to http://localhost:3000/tasks/show/1 we should be getting.

{"id":1,"title":"Write blog post","description":"about rabl"}

The first thing you can notice is that we no longer have the square brackets meaning that we only fetched one record.

So it's going all pretty nicely, now… what if we want to create records via API? It's also really easy.

app/controllers/task.rb

post :create do
  @task = Task.create(params)

  render 'tasks/create'
end

We catch the params and we just use it to create the new record, that's it. But we also need a template to render the response.

app/views/tasks/create.rabl

object @task

extends '/tasks/show'

Ok, time to explain the node erros from the template show. When trying to create a record if we don't pass validation or we get another kind of error, ActiveRecord returns us an instance of that intended record, but unsaved, and with an errors attribute that contains an array of errors. It is important to have that node so we can inform the consumer of that endpoint what happened with the request.

Now, how do we test this?

In order to not have to use browser tools for posting to that endpoint and also keep working with Ruby, we will use this awesome gem called Weary, that I will not describe in detail (maybe in another blogpost).

To install Weary into our application we need to add it to the Gemfile and run "bundle install"

gem 'weary'

We create a proxy for the tasks.

lib/task_proxy.rb

class TaskProxy < Weary::Client
  get :index, "http://localhost:3000/tasks"

  get :show, "http://localhost:3000/tasks/show/{id}" do |resource|
    resource.required :id
  end

  post :create, 'http://localhost:3000/tasks/create/' do |resource|
    resource.required :title, :user_id, :due_time

    resource.optional :description
  end
end

Having this done we can at the same time in another terminal run the Padrino console and type this:

TaskProxy.new.create(title: 'Call mom', due_time: 3.days.from_now, user_id: 2).perform

returning something like this

=> #<Weary::Response:0x007fa712d03f20 @response=#<Rack::Response:0x007fa712d03e30 @status=200, @header={"content-type"=>"text/html;charset=utf-8", "server"=>"WEBrick/1.3.1 (Ruby/1.9.3/2012-04-20)", "date"=>"Mon, 15 Oct 2012 15:16:39 GMT", "connection"=>"close", "Content-Length"=>"47"}, @chunked=false, @writer=#<Proc:0x007fa712d02ad0@/Users/fcastellanos/.rbenv/versions/1.9.3-p194/lib/ruby/gems/1.9.1/gems/rack-1.4.1/lib/rack/response.rb:28 (lambda)>, @block=nil, @length=47, @body=["{\"id\":12,\"title\":\"Call mom\",\"description\":null}"]>, @status=200>

Notice that @body has the response fro the post we just created and that @status is 200.

So, when we hit the browser again at http://localhost:3000/tasks/ we should be seeing three tasks now.

[{"id":1,"title":"Write blog post","description":"about rabl","due_time":"2012-10-14T21:58:39-07:00","on_time":false,"user":{"first_name":"Fernando"}},{"id":2,"title":"Pay bills","description":"Cable and gas","due_time":"2012-10-16T23:16:38-07:00","on_time":true,"user":{"first_name":"John"}},{"id":11,"title":"Call mom","description":null,"due_time":"2012-10-18T08:15:42-07:00","on_time":true,"user":{"first_name":"John"}},{"id":12,"title":"Call mom","description":null,"due_time":"2012-10-18T08:16:39-07:00","on_time":true,"user":{"first_name":"John"}}]

And that's it! we can now create, find, and get a list of tasks. I hope you found this useful.

https://github.com/fcastellanos/padrino_rabl/

blog comments powered byDisqus