#348 The Rails API Gem
- Download:
- source codeProject Files in Zip (32 KB)
- mp4Full Size H.264 Video (27.2 MB)
- m4vSmaller H.264 Video (13.9 MB)
- webmFull Size VP8 Video (13.7 MB)
- ogvFull Size Theora Video (33.5 MB)
With rich client-side applications becoming more popular a common question is “Should I use Rails if all I need to do is provide a JSON API”? If you need to develop such an API you should take a look at the Rails::API gem by Rails core member Santiago Pastorino. The README for this gem goes into detail and describes what Rails gives us that’s useful if we just want to build an API. Rails is very modular and this gem provides a generator for creating a streamlined Rails application that strips out everything we don’t need if we’re building an API. In this episode we’ll use it with a very simple application to see how it works.
Getting Started With Rails::API
First we’ll need to install the gem which we do by running the following command.
$ gem install rails-api
If you’re running rbenv you’ll need to run rbenv rehash
go get access to the rails-api
command. We can use this command to create a new application just like we would with rails
and we’ll call our application todo.
$ rails-api new todo
This command generates a streamlined application designed for providing an API. One of the major differences we’ll notice is that the /app
directory is much simpler. There are no assets
or views
directories in it, just controllers
, mailers
and models
. If we look at the ApplicationController
that’s generated we’ll see that it inherits from ActionController::API
which is more lightweight than ActionController::Base
.
class ApplicationController < ActionController::API end
We’ll go into more detail about these differences later.
Another major difference is in the gemfile. The rails-api
gem is included here but there’s no assets
group. The jquery-rails
gem is still listed but we can remove that and it won’t be included in future versions of Rails::API.
source 'https://rubygems.org' gem 'rails', '3.2.3' # Bundle edge Rails instead: # gem 'rails', :git => 'git://github.com/rails/rails.git' gem 'rails-api' gem 'sqlite3'
There’s also a difference in how the generators work. If we try generating some scaffolding for a Task
model far fewer files are generated as there are no template or asset files.
$ rails g scaffold task name invoke active_record create db/migrate/20120517174851_create_tasks.rb create app/models/task.rb invoke test_unit create test/unit/task_test.rb create test/fixtures/tasks.yml route resources :tasks, except: :edit invoke scaffold_controller create app/controllers/tasks_controller.rb invoke test_unit create test/functional/tasks_controller_test.rb
Having created this scaffold we’ll migrate the database next so that we can use it.
$ rake db:migrate
If we look at the TasksController
that was generated by the scaffold we’ll see that it only renders a JSON response.
class TasksController < ApplicationController # GET /tasks # GET /tasks.json def index @tasks = Task.all render json: @tasks end # GET /tasks/1 # GET /tasks/1.json def show @task = Task.find(params[:id]) render json: @task end # Other actions omitted. end
These apps aren’t designed to render an HTML view or user interface so the controller actions just return JSON.. If we try visiting the /tasks
path in a browser we’ll just see the JSON output which will be an empty array as we don’t currently have any tasks.
Creating a UI That Uses The API
If we want to present a user interface for creating and managing tasks this will need to be done either in the /public
directory or outside of this application entirely.
For the purposes of this application we’ll replace the contents of the default index.html
file with one that will list the tasks and allow us to create new ones along with a dabble of JavaScript and some CSS.
<!DOCTYPE html> <html> <head> <title>To Do List</title> <style type="text/css" media="screen"> html, body { background-color: #4B7399; font-family: Verdana, Helvetica, Arial; font-size: 14px; } a { color: #0000FF; } #container { width: 75%; margin: 0 auto; background-color: #FFF; padding: 20px 40px; border: solid 1px black; margin-top: 20px; } </style> <script src="//ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js" type="text/javascript"></script> <script type="text/javascript" charset="utf-8"> $(function() { function addTask(task) { $('#tasks').append('<li>' + task.name + '</ul>'); } $('#new_task').submit(function(e) { $.post('/tasks', $(this).serialize(), addTask); this.reset(); e.preventDefault(); }); $.getJSON('/tasks', function(tasks) { $.each(tasks, function() { addTask(this); }); }); }); </script> <body> <div id="container"> <h1>To-Do List</h1> <form id="new_task"> <input type="text" name="task[name]" id="task_name"> <input type="submit" value="Add"> </form> <ul id="tasks"></ul> </div> </body> </html>
We’ve put everything inline but of course you can structure this however you like. At the root of this application we now have a page where we can add items to a list and they’ll be saved through the API. We can reload the page to fetch the saved items via the API too.
This is done by the jQuery code on our page. We call $.getJSON
to fetch the tasks and $.post
to create a task.
$('#new_task').submit(function(e) { $.post('/tasks', $(this).serialize(), addTask); this.reset(); e.preventDefault(); }); $.getJSON('/tasks', function(tasks) { $.each(tasks, function() { addTask(this); }); });
We’ve deliberately kept this example simple but we could use a full client-side framework such as Backbone.js.
Handling Additional Formats
Next we’ll show you some of the things that the Rails::API controller leaves out. Let’s say that we want our API to respond to different formats so that it can return a list of tasks in XML as well as in JSON. Normally we’d to this by adding a respond_to
block, like this:
def index @tasks = Task.all respond_to do |format| format.json { render json: @tasks } format.xml { render xml: @tasks } end end
If we visit http://localhost:3000/tasks.xml
now we’d expect to see some XML but instead we see the error undefined method `respond_to’
. The Rails::API controller is missing a number of features to make it more lightweight but we can easily add them back if we need to. We need to modify the TasksController
and include a module called ActionController::MimeResponds
.
class TasksController < ApplicationController include ActionController::MimeResponds # Actions omitted end
If we wanted other controllers to be able to return XML we could include this module in the ApplicationController
instead. When we reload the page now we’ll see our task rendered as XML.
The Rails::API README has a list of modules that we might want to include. Along with ActionController::MimeResponds
we can include translations, Basic HttpAuthentication and so on. This isn’t the full list of modules, though. We can look at the source code to compare the modules included in a Rails::API controller with those included in ActionController::Base5. If we included all the modules from ActionController::Base we’d have duplicated its functionality. This way we can pick and choose exactly what we want.
Sometimes, though, we want to do more than just include a module in a controller. For example if we want to add cookie support we need to include a module and also add some Rack middleware. This brings up an important point: not only does Rails API slim down the controllers but also the Rack middleware stack. If we go into our Rails application and run rake middleware
we’ll get a list of the middleware that it uses. This will be shorter than the equivalent list for a normal Rails app.
$ rake middleware use ActionDispatch::Static use Rack::Lock use #<ActiveSupport::Cache::Strategy::LocalCache::Middleware:0x007f9704a76118> use Rack::Runtime use ActionDispatch::RequestId use Rails::Rack::Logger use ActionDispatch::ShowExceptions use ActionDispatch::DebugExceptions use ActionDispatch::RemoteIp use ActionDispatch::Reloader use ActionDispatch::Callbacks use ActiveRecord::ConnectionAdapters::ConnectionManagement use ActiveRecord::QueryCache use ActionDispatch::ParamsParser use ActionDispatch::Head use Rack::ConditionalGet use Rack::ETag run Todo::Application.routes
We’re missing five pieces of middleware here. If we diff the output from rake middleware
on a full rails app and on ours, the missing ones are these:
> use Rack::MethodOverride > use ActionDispatch::Cookies > use ActionDispatch::Session::CookieStore > use ActionDispatch::Flash > use ActionDispatch::BestStandardsSupport
If you’re wondering what any of this middleware does take a look at episode 319 for details.
To add some of this functionality back in, such as the cookie support, we’ll need to add the middleware and include the controller module. We’ll add the middleware in the application config file, like this:
module Todo class Application < Rails::Application config.middleware.insert_after ActiveRecord::QueryCache, ActionDispatch::Cookies # Rest of class omitted end end
It’s a good idea to try to maintain the same order of middleware as in the full stack so we’ve used insert_after
to add the Cookies middleware after QueryCache
where is it in the full stack. Now in the TasksController
we can include the Cookies
module to add the cookie behaviour.
class TasksController < ApplicationController include ActionController::MimeResponds include ActionController::Cookies # Actions omitted end
We’ll need to restart our app for the changes to the middleware to be picked up. When we do and we reload the page we get an error message telling is that helper_method
isn’t defined.
This brings up an important point. Some modules depend on other modules. In this case Cookies
depends on the Helpers
module so we’ll need to add this as well.
class TasksController < ApplicationController include ActionController::MimeResponds include ActionController::Helpers include ActionController::Cookies # Actions omitted end
When we reload the page now it works again. Now we can use cookies just as we would in a full Rails application.
Using JBuilder and RABL to Generate JSON
Next we’ll discuss some other gems that can help us to build a JSON API. We’ve covered Jbuilder and RABL in previous episodes. These gems will render JSON using view templates. This rather goes against the grain of Rails::API but we can use them if we want to. We’ll demonstrate this with RABL. As ever we’ll need to add its gem to the gemfile then run bundle to install it.
gem 'rabl'
As our /app
directory doesn’t have a views directory we’ll need to create one as RABL works with view templates. We’ll need to create a tasks
directory under views and an index.json.rabl
file in here. In this file we’ll tell RABL to render any tasks as a collection with id
and name
attributes.
collection @tasks attributes :id, :name
In the controller we need to change the JSON rendering behaviour so that it renders this template. In a normal Rails application we could remove the render
call completely and it will implicitly fall back to rendering the template. A Rails::API app
expects explicit rendering behaviour but all we need to do is call render
here and it will render the template matching the current action.
def index @tasks = Task.all respond_to do |format| format.json { render } format.xml { render xml: @tasks } end end
We’ll need to restart the application again but after we have we can visit /tasks.json
and we’ll see the JSON response rendered through RABL.
It get implicit rendering back we can include the ImplicitRender
module and then we won’t need to call render
any more. If we add an XML template we’ll be able to remove the entire respond_to
block as the format will be detected and the appropriate template rendered automatically.
This is getting closer to a full Rails application here as we’re doing implicit rendering of view templates. If we’re adding all this functionality back in we might wonder whether it’s still worth using Rails::API. Given what we’ve done here we’d probably be better using a full Rails app as we’re doing view rendering, even though we’re just rendering an API. The choice comes down to each application’s requirements and how much of the Rails stack we plan to use.
If you are building a JSON API it’s well worth reading the “Why use Rails for JSON APIs?” section of the README. It goes into a lot of detail on what Rails does provide outside HTML rendering.