#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)
Les applications clientes riches devenant de plus en plus populaires, il y a une question récurrente : "Dois-je utiliser Rails si tout ce dont j'ai besoin est de fournir une API JSON ?" Si vous avez besoin de développer une telle API, vous devriez jeter un oeil à Rails::API gem par Santiago Pastorino, développeur de l'équipe Rails. Le README pour cette gemme rentre dans les détails et décrit ce que Rails nous donne d'utile si on veut simplement développer une API. Rails est très modulaire et cette gemme fournit un générateur pour créer une application simplifiée qui est dépouillé de tout ce dont on n'a pas besoin quand on construit une API. Dans cet épisode, nous allons l'utiliser dans une application vraiment simple pour voir comment ça marche.
Débuter avec Rails::API
D'abord, nous avons besoin d'installer la gemme ce qu'on fait en exécutant la commande suivante :
$ gem install rails-api
Si vous tournez avec `rbenv` vous aurez besoin de lancer rbenv rehash
pour accéder à la commande rails-api
. Nous pouvons utiliser cette commande pour créer une application aussi simplement qu'on le ferait avec rails
et nous allons appeler notre application `todo`.
$ rails-api new todo
Cette commande gén!re a une application simplifiée conçue pour fournir une API. Une des principales différences qu'on peut noter est que le dossier /app
est beaucoup plus simple. Il n'y a pas de répertoires assets
ou views
, juste controllers
, mailers
et models
. Si on regarde ApplicationController
qui a été généré, nous voyons qu'il hérite de ActionController::API
qui est beaucoup
plus léger que ActionController::Base
.
class ApplicationController < ActionController::API end
Nous reparlerons de ces différences plus tard.
Une autre grande différence est dans le gemfile. La gemme rails-api
est inclue ici mais il n'y a pas de groupe assets
. La gemme jquery-rails
est toujours présente mais on peut la supprimer. Elle ne sera plus comprise dans les futures versions de 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'
Il y a aussi une différence dans la façon de fonctionner des générateurs. Si on essaie de générer un scaffold (ou échafaud…) pour un modèle Task
, beaucoup moins de fichiers sont générés puisqu'il n'y a aucun fichiers de templates ou d'assets.
$ 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
Après avoir créer ce scaffold, nous allons migrer la base de données afin de pouvoir l'utiliser.
$ rake db:migrate
Si on regarde TasksController
qui a été généré par le scaffold, nous pouvons voir qu'il n'y a que des rendus de réponses JSON.
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
Ces applications ne sont pas conçues pour rendre une vue HTML ou une interface utilisateur donc les actions du contrôleur ne renvoient que du JSON. Si on essaie de visiter l'url /tasks
dans un navigateur, nous verrons simplement une sortie JSON qui sera un tableau vide puisqu'on a toujours aucune tâche.
Créer une UI qui utilise l'API
Si on veut présenter une UI pour créer et gérer les tâches, cela devra être fait soit dans le répertoire /public
soit en dehors de l'application. Pour les objectifs de cette application, nous allons modifier le fichier par défaut index.html
de façon à lister les tâches et nous permettre d'en créer d'autres avec un peu de JavaScript et un peu de 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>
Nous avons tout mis dans ce fichier mais il est évident que vous pouvez faire comme bon vous semble. À la racine de cette application, nous avons maintenant une page où nous pouvons ajouter des items à une liste et ils seront sauvegardés via l'API. On peut recharger la page pour récupérer les items enregistrés grâce à l'API également.
Dans notre cas, on a utilisé jQuery. Nous appelons $.getJSON
pour récupérer les tâches et $.post
pour créer une tâche.
$('#new_task').submit(function(e) { $.post('/tasks', $(this).serialize(), addTask); this.reset(); e.preventDefault(); }); $.getJSON('/tasks', function(tasks) { $.each(tasks, function() { addTask(this); }); });
Il est volontaire de garder cet exemple simple mais on pourrait utiliser un framework côté client complet tel que Backbone.js.
Gérer plus de formats
Ensuite, nous allons voir quelques choses que le contrôleur Rails::API laisse de côté. Disons que nous voulons que notre API réponde à différents formats afin que ça puisse renvoyer une liste de tâches en XML aussi bien qu'en JSON. Normalement, nous aurions fait ceci en ajoutant un bloc respond_to
, comme ceci :
def index @tasks = Task.all respond_to do |format| format.json { render json: @tasks } format.xml { render xml: @tasks } end end
Si nous visitons http://localhost:3000/tasks.xml
, nous nous attendons à voir du XML mais nous obtenons une erreur undefined method `respond_to’
. Le contrôleur Rails::API manque d'un certain nombre de fonctionnalités afin de le rendre plus léger mais nous pouvons les ajouter facilement si nous en avons besoin. Nous avons besoin de modifier le contrôleur TasksController
et inclure un module appelée ActionController::MimeResponds
.
class TasksController < ApplicationController include ActionController::MimeResponds # Actions omitted end
Si nous voulions que d'autres contrôleurs soient capable de renvoyer du XML nous pourrions inclure ce module dans ApplicationController
à la place. Quand on recharge la page, nous voyons maintenant que nos tâches sont rendues au format XML.
Le README de Rails::API a une liste de modules que nous pourrions vouloir inclure. En plus de ActionController:MimeResponds
, nous pouvons inclure les traductions, Basic HttpAuthentication
et ainsi de suite. Il ne s'agit d'une liste exhaustive des modules. Nous pouvons jeter un oeil au code source pour comparer les modules inclus dans un contrôleur Rails::API avec ceux inclus dans ActionController:Base5. Si nous incluions tous les modules de ActionController::Base, nous dupliquerions ses fonctionnalités. De cette façon, nous pouvons choisir ce dont on a exactement besoin.
Parfois, pourtant, nous voulons faire plus qu'inclure un module dans un contrôleur. Par exemple, si nous voulons ajouter le support des cookies, nous avons besoin d'inclure un module et aussi ajouter un middleware Rack. Ceci amène un point important : non seulement Rails API allège les contrôleurs mais également la pile de middleware Rack. Si nous allons dans le code de notre application Rails et que nous exécutons la commande rake middleware
, nous obtenons une liste des
middleware utilisés. La liste sera plus courte que son équivalent pour une application Rails normale.
$ 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
Il nous manque 5 middlewares. Si nous fait le différentiel avec la sortie de la commande rake middleware
sur une application rails complète, les manquants sont :
> use Rack::MethodOverride > use ActionDispatch::Cookies > use ActionDispatch::Session::CookieStore > use ActionDispatch::Flash > use ActionDispatch::BestStandardsSupport
Si vous vous demandez ce que font ces middlewares, jetez un oeil à l'épisode 319 pour plus de détails.
Pour rajouter certaines de ces fonctionnalités, tel que le support de cookie, nous allons avoir besoin d'ajouter le middleware et d'inclure le contrôleur du module. Nous allons ajouter le middleware dans le fichier de config comme suit :
module Todo class Application < Rails::Application config.middleware.insert_after ActiveRecord::QueryCache, ActionDispatch::Cookies # Rest of class omitted end end
C'est une bonne idée d'essayer de maintenir le même ordre de middleware que dans une pile complète, c'est pourquoi on utilise insert_after
pour ajouter le middleware Cookies
après QueryCache
où il est situé dans une pile complète. Maintenant, dans le contrôleur TasksController
, nous pouvons inclure le module Cookies
pour ajouter le comportement cookie
.
class TasksController < ApplicationController include ActionController::MimeResponds include ActionController::Cookies # Actions omitted end
Nous avons maintenant besoin de redémarrer l'application pour que les changements de middleware soient pris en compte. Une fois fait, et qu'on a rechargé la page, on obtient une erreur qui dit que helper_method
n'est pas défini.
Cela soulève un point important. Certains modules dépendent d'autres modules. Dans ce cas, Cookies
dépend du module Helpers
, donc nous allons avoir besoin de l'ajouter également.
class TasksController < ApplicationController include ActionController::MimeResponds include ActionController::Helpers include ActionController::Cookies # Actions omitted end
Quand on recharge la page, tout fonctionne de nouveau. Maintenant, nous pouvons utiliser les cookies de nouveau comme nous le ferions dans une application Rails complète.
Utiliser JBuilder et RABL pour générer du JSON
Maintenant, nous allons discuter de quelques gemmes qui peuvent nous aider à construire notre API JSON. Nous avons vu Jbuilder et RABL dans des épisodes précédents. Ces gemmes rendront du JSON en utilisant des templates. Ça va un peu à l'encontre du grain de Rails::API mais nous pouvons les utiliser si on le veut. Nous allons le démontrer avec RABL. Comme toujours, nous
allons devoir ajouter cette gemme au gemfile et lancer bundle
pour l'installer.
gem 'rabl'
Comme notre répertoire /app
n'a pas de répertoire views
, nous allons le créer puisque RABL marche avec des templates de vues. Nous allons devoir créer un répertoire tasks
dans views
et un fichier index.json.rabl
dedans. Dans ce fichier, nous allons dire à RABL de rendre les tâches comme une collection avec les attributs id
et name
.
collection @tasks attributes :id, :name
Dans le contrôleur, nous avons besoin de changer le rendu de JSON pour qu'il puisse rendre ce template. Dans une application Rails normale, nous pourrions supprimer complètement l'appel à render
et il renderait automatiquement le template. Une application Rails::API s'attends explicitement à connaitre son comportement de rendu mais tout ce dont nous avons besoin pour se faire est d'appeler render
ici et il rendera le template correspond à l'action.
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
Nous avons besoin de redémarrer l'application encore mais ensuite nous pourrons visiter /tasks.json
et nous verrons la réponse JSON rendue via RABL.
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.
Nous pouvons inclure le module ImplicitRender
et nous n'aurons pas besoin d'appeler la méthode render
. Si nous ajoutons un template XML nous serions en mesure de supprimer complètement le bloc respond_to
puisque le format sera détecter automatiquement et le template approprié sera rendu.
Ceci se rapproche d'une application Rails complète puisque nous faisons du rendu implicite de templates. Si nous ajoutons toutes ces fonctionnalités, nous devrions peut être nous demander si cela reste valable d'utiliser Rails::API. Étant donné ce que nous avons fait jusque là, nous aurions probablement mieux fait d'utiliser une application Rails complète puisque nous faisons du rendu de vue, même si nous ne rendons qu'une API. Le choix vient à chaque prérequis pour l'application et ce que nous prévoyons d'utiliser de la pile Rails.
Si vous développez une API JSON, cela vaut le coup que vous lisiez la section “Why use Rails for JSON APIs?” du README. Elle rendre dans beaucoup de détail sur ce que Rails fournit en plus du rendu HTML.