#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)
Z czasem jak aplikacje po stronie klienta zyskały na popularności, częstym pytaniem jest “Czy powinno się używać Railsów jeśli wszystko czego potrzebuję dostarczne jest poprzez API w postaci plików JSON”? Jeśli potrzebujesz opracować takie API powinieneś zobaczyć gem Rails::API stworzony przez Santiago Pastorino, członka "Rails core". Plik README tego gema zawiera szczegółowy opis jak Railsy udostępniają przydatne funkcje do budowy API. Railsy są bardzo modularne, gem ten dostarcza generator do tworzenia uproszczonej Railsowej aplikacji, która usuwa wszystko czego nie potrzebujemy przy budowie API. W tym epizodzie przy wykorzystaniu wpomnianego gema stworzymy bardzo prostą aplikacją, w celu pokazania jak to działa.
Zaczynamy z Rails::API
Na początku potrzebujemy zainstalować gem, wykonamy to za pomocą poniższej komendy.
$ gem install rails-api
Jeśli używasz rbenv powinieneś uruchomić rbenv rehash
w celu uzyskania dostępu do komendy rails-api
. Możemy użyć tej komendy do utworzenia nowej aplikacji tak samo jak robiliśmy to z rails
. Tworzymy nową aplikację todo.
$ rails-api new todo
Powyższa komenda utworzy okrojoną aplikację zaprojektowaną w celu dostarczenia API. Jedną z głównych różnic jest to, że katalog /app
jest dużo prostszy. Nie ma katalogów assets
i views
, są tylko controllers
, mailers
i models
. Jeśli przyjrzymy się wygenerowanemu ApplicationController
zauważymy, że dziedziczy on po ActionController::API
, który jest lżejszy od ActionController::Base
.
class ApplicationController < ActionController::API end
Więcej szczegółów zostanie opisana później.
Kolejna główna różnica znajduje się w pliku gemfile. Gem rails-api
zawarty jest w pliku, ale nie ma grupy assets
. Gem jquery-rails
jest nadal obecny na liście, ale możemy usunąć go, nie będzie zawarty w przyszłych wersjach 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'
Różnica występuje również w sposobie działania generatorów. Jeśli spróbujemy wygenerować pewną strukturę plików dla modelu Task
zauważymy, że nie ma tam szablonów ani plików asset.
$ 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
Posiadając utworzoną strukturę, uruchamiamy migrację bazy danych.
$ rake db:migrate
Jeśli zajrzymy do TasksController
, który został utworzony w trakcie generowania struktury modelu, zauważymy, że generuje odpowiedzieć 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
Ta aplikacja nie została zaprojektowana do wyświetlania widoku HTML lub interfejsu użytkownika, więc akcje kontorlera zwracają tylko JSONa. Jeśli spróbujemy otworzyć /tasks
, w przeglądarce, zobaczymy tylko JSONa, który będzie pustą tablicą ponieważ nasza baza danych jest pusta.
Tworzenie interfejsu użytkownika, używanego przez API
Jeśli chcemy użyć interfejsu użytkownika do tworzenia i zarządzania zadaniami, musi być on wykonany w katalogu /public
lub na zewnątrz całej aplikacji.
Dla celów tej aplikacji zmodyfikowaliśmy zawartość domyślnego pliku index.html
, który zawiera listę zadań i pozwala na utworzenie nowego zadania z wykorzystaniem JavaScript i niewielkiej ilości kodu 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>
Umieściliśmy wszystko w jednym pliku ale możesz podzielić powyższy kod do wielu plików, tak żeby otrzymać interesującą nas strukturę. W głównej części aplikacji mamy stronę, na której możemy dodawać nową pozycje do listy i zostanie ona zapisane w wykorzystaniem API. Możemy przeładować stronę w celu pobrania, zapisanych, pozycji z wykorzystaniem API.
Wykonano to przy użyciu kodu jQuery na naszej stronie. Możemy wywołać $.getJSON
w celu pobrania zadań i $.post
w celu utworzenia zadania.
$('#new_task').submit(function(e) { $.post('/tasks', $(this).serialize(), addTask); this.reset(); e.preventDefault(); }); $.getJSON('/tasks', function(tasks) { $.each(tasks, function() { addTask(this); }); });
Przykład jest celowo prosty ale możemy wykorzystać pełny framework po stronie klienta jak na przykład Backbone.js.
Dodatkowe formaty odpowiedzi
Następnie pokażemy parę brakujących rzeczy w kontrolerze Rails::API. Powiedzmy, że chcemy aby nasze API odpowiadało w różnych formatach, tak aby mogło zwrócić listę zadań również w postaci XMLa, tak samo jak to robi w JSONie. Normalnie aby to osiągnąć dodajemy blok respond_to
jak poniżej
def index @tasks = Task.all respond_to do |format| format.json { render json: @tasks } format.xml { render xml: @tasks } end end
Odwiedzając stronę http://localhost:3000/tasks.xml
oczekujemy, że zobaczymy pewną strukturę XML, niestety zamiast tego widzimy błąd undefined method `respond_to’
. W kontrolerze Rails::API brakuje wielu udogodnień co czyni go bardziej lżejszym, możemy jednak przywrócić je jeśli ich potrzebujemy. Wymagane jest zmodyfikowanie TasksController
i załączenie modułu zwanego ActionController::MimeResponds
.
class TasksController < ApplicationController include ActionController::MimeResponds # Actions omitted end
Jeśli chcemy aby inne kontrolery mogły zwracać XMLa, możemy załączyć ten moduł w ApplicationController
. Po odświeżeniu strony zobaczymy wygenerowaną strukturę XML dla naszego zadania.
Plik README w Rails::API zawiera listę modułów, które moglibyśmy załączyć. Poza ActionController::MimeResponds
możemy załaczyć tłumaczenia, Basic HttpAuthentication i wiele więcej. To nie jest pełna lista dostępnych modułów. Możemy przejrzeć kod źródłowy aby porównać załączone moduły w kontrolerze Rails::API z załączonymi w ActionController::Base. Jeśli załączomy wszystkie moduły z ActionController::Base będziemy mieli zduplikowaną funkcjonalność. W ten sposób możemy wykorzystać tylko to co nas interesuje.
Czasami, chcemy zrobić więcej niż tylko załączyć moduł w kontrolerze. Przykładowo, jeśli chcemy dodać obsługę ciasteczek, wymagane jest załączenie modułu i dodanie middleware'a Racka. Ważna rzecz: nie tylko rozszerzamy API w kontrolerze ale również stos middlewarów Racka. Po przejściu do naszej aplikacji i uruchomieniu rake middleware
otrzymamy listę używanych middlewarów. Lista ta będzie krótsza od podobnej listy w pełnej aplikacji Railsowej.
$ 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
Pomineliśmy tutaj pięć rzeczy. Jeśli porównamy wyjście rake middleware
z pełną railsową aplikacją, brakuje następujących pozycji:
> use Rack::MethodOverride > use ActionDispatch::Cookies > use ActionDispatch::Session::CookieStore > use ActionDispatch::Flash > use ActionDispatch::BestStandardsSupport
Jeśli zastanawiasz się co każdy middleware robi, zobacz szczegóły w epizodzie 319.
Aby przywrócić niektóre z tych funkcjonalności, jak np. obsługa ciasteczek, będziemy musieli dodać middleware a następnie załączyć odpowiedni moduł w kontrolerze. Dodajemy middleware do plików konfiguracyjnych aplikacji, jak poniżej:
module Todo class Application < Rails::Application config.middleware.insert_after ActiveRecord::QueryCache, ActionDispatch::Cookies # Rest of class omitted end end
Dobrym pomysłem jest ustawienie middleware'ów w takiej kolejności jak to jest w liście middlewarów z pełnej Railsowej aplikacji, używamy więc insert_after
aby dodać middleware ciasteczek po QueryCache
, zgodnie z kolejnością w stosie pełnej Railsowej aplikacji. Teraz w TasksController
możemy załączyć moduł Cookies
w celu dodania obsługi ciasteczek.
class TasksController < ApplicationController include ActionController::MimeResponds include ActionController::Cookies # Actions omitted end
Musimy teraz zresetować naszą aplikację aby zastosować wprowadzone zmiany w obsłudze middlewarów. Kiedy to zrobimy a następnie przeładujemy stronę otrzymamy komunikat błędu mówiący, że metoda helper_method
jest nie zdefiniowana.
Kolejna ważna rzecz. Niektóre moduły zależą od innych modułów. W naszym przypadku Cookies
zależy od modułu Helpers
, musimy więc dodać również ten moduł.
class TasksController < ApplicationController include ActionController::MimeResponds include ActionController::Helpers include ActionController::Cookies # Actions omitted end
Po przeładowaniu strony znowu wszystko działa prawidłowo. Teraz możemy używać ciasteczek tak samo jak w pełnej Railsowej aplikacji.
Używamy JBuildera i RABLa do generowania JSONa
Następnie porozmawiamy o innych gemach, które mogą być przydatne przy budowaniu API JSONa. Zobaczmy Jbuilder i RABL w poprzednich epizodach. Wspomniane gemy generują JSONa używając szablonów widoku. Jest to rzadko używane zamiast Rails::API, ale możemy wykorzystać takie podejście jeśli chcemy. Zademonstrujemy wykorzystanie RABLa. Jak zawsze musimy dodać ten gem do pliku gemfile i uruchomić bundle w celu zainstalowania gema.
gem 'rabl'
W związku z tym, że nasz katalog /app
nie zawiera katalogu views, musimy go stworzyć, ponieważ RABL wymaga szablonów widoku. Musimy utworzyć katalog tasks
w katalogu views
oraz plik index.json.rabl
. W tym pliku mówimy RABLowi, żeby generował kolekcję zadań z atrybutami id
i name
.
collection @tasks attributes :id, :name
W kontrolerze musimy zmienić sposób generowania obiektu JSONa przy wykorzystaniu szablonu. W normalnej Railsowej aplikacji możemy całkowicie usunąć wywołanie render
aby wynik był generowanie na podstawie szablonów. Aplikacja Rails::API
oczekuje podania konkretnego sposobu generowania wyniku, ale wszystko co potrzebujemy to wywołanie render
i wynik generowany będzie z wykorzystaniem szablonu przypisanego do konkretnej akcji.
def index @tasks = Task.all respond_to do |format| format.json { render } format.xml { render xml: @tasks } end end
Musimy ponownie zresetować aplikację, po uruchomieniu /tasks.json
zobaczymy odpowiedź w postani obiektu JSONa generowaną przez RABLa.
Możemy przywrócić domyślne generowanie poprzez załączenie moduł ImplicitRender
i wtedy nigdy więcej nie będziemy musieli wywoływać render
. Jeśli dodamy szablon XML będziemy mogli całkowicie usunąć cały blok respond_to
, format żądanej odpowiedzi zostanie automatycznie wykryty i zwrócony prawidłowy wynik.
Zbliżamy się coraz bardziej do pełnej Railsowej aplikacji, ponieważ do generowania wyjścia wykorzystujemy teraz szablony widoku. Jeśli przywrócimy całą funkcjonalność z powrotem, nadal warto wykorzystać Rails::API. To co tutaj zrobiliśmy prawdopodobnie lepiej byłoby zrealizować przy wykorzystaniu pełnej aplikacji Railsowej jak na przykład generowanie widoku, nawet jeśli generujemy tylko API. Wyboru należy dokonać dla każdej aplikacji z osobna, biorąc pod uwagę jej wymagania oraz jak wiele rzeczy z Railsów planujemy wykorzystać.
Jeśli budujesz JSON API, warto przeczytać sekcję “Dlaczego użyć Railsów do JSON API?” w pliku README. Opisanych jest wiele szczegółów w Railsach, które są wspierane poza generowaniem widoku HTML.