#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)
클라이언트사이드 어플리케이션이 최근 인기를 얻고 있는데, 필요로 하는 것이 JSON API를 제공하는 것일 때 레일스를 사용해야 하는가에 대한 의문점이 듭니다. 이와 같이 API 어플리케이션이 필요한 한 경우에는 레일스 핵심 멤버인 Satiago Pastrorino가 만든 Rails::API gem을 검토해 보아야 합니다. 이 젬의 README 파일에는 API 어플리케이션이 필요할 때 레일스가 주는 이점에 대한 자세한 내용이 기술되어 있습니다. 레일스 프레임워크는 모듈화되어 있기 때문에, 이 젬에서는 API 어플리케이션을 작성하는데 불필요한 것들을 제외한 상태에서 레일스 어플리케이션을 만들수 있도록 제너레이터를 제공해 줍니다. 이번 연제에서는 이 젬을 이용하여 매우 간단한 어플리케이션을 작성할 것이며 어떻게 작동하는지를 알아 볼 것입니다.
Rails::API로 시작하기
먼저 아래와 같이 명령을 실행하여 젬을 설치할 필요가 있습니다.
$ gem install rails-api
rbenv을 사용하는 경우에는 rbenv rehash
를 실행하여 rails-api
명령을 사용할 수 있도록 해야 합니다. 이 명령을 이용해서 rails
명령으로 하듯이 API 어플리케이션을 생성할 수 있습니다. 어플리케이션의 이름을 todo 라고 하겠습니다.
$ rails-api new todo
이 명령을 실행하면 API를 제공하도록 디자인된 어플리케이션을 생성하게 됩니다. 주요 차이점 중에 하나는 /app
디렉토리가 매우 간단하다는 것입니다. 즉, assets
또는 views
디렉토리가 없고 단지 controllers
, mailers
, 그리고 models
디렉토리만 있습니다. 생성된 ApplicationController
를 보면, 이 클래스가 ActionController::Base
보다는 훨씬 가벼운 ActionController::API
로부터 상속받는다는 것을 알게 될 것입니다.
class ApplicationController < ActionController::API end
나중에 이러한 차이점에 대해서 자세하게 설명할 것입니다.
또 다른 주요 차이점은 gemfile에 있습니다. rails-api
젬이 여기에 포함되어 있지만 assets
그룹이 존재하지 않습니다. jquery-rails
젬이 여전히 포함되어 있을 수 있지만 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'
제너레이터의 결과물에도 차이가 있습니다. Task
모델에 대해서 scaffold 제너레이터를 사용할 경우 템플릿이나 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
모델 scaffolding 후에 데이터베이스를 마이그레이션하여 사용할 준비를 합니다.
$ rake db:migrate
scaffolding으로 생성된 TasksController
를 보면 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
이 어플리케이션은 HTML 뷰나 사용자 인터페이스를 렌더링하지 않도록 디자인되기 때문에 해당 컨틀로러는 JSON 파일만을 반환하게 될 것입니다. 브라우저에서 /tasks
를 요창하게 되면 JSON 결과물만을 보게 되는데, 아직 어떤한 task도 등록되어 있지 않기 때문에 빈 배열만 존재하게 됩니다.
API를 사용하여 UI 작성하기
사용자 인터페이스를 작성하여 task를 작성하고 관리하고자 할 경우에는, /public
디렉토리나 이 어플리케이션 외부에 작성을 해야 합니다. 이 어플리케이션의 목적상 디폴트 문서인 index.html
파일의 컨텐츠를 자바스크립트와 CSS를 이용하여 task 목록을 보여주고 새로운 task를 추가하는 내용으로 변경할 것입니다.
<!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>
여기서는 모든 것을 인라인으로 작성했지만 원하는데로 구조를 변경하여 작성할 수 있습니다. 이 어플리케이션의 루트에 웹페이지를 두어 API를 통해서 항목을 목록에 추가하고 저장할 수 있도록 합니다. 물론 브라우저를 다시 로딩하여 API를 통해서 저장된 항목을 불러올 수도 있습니다.
이 페이지에서는 jQuery 코드를 이요하여 작업을 했습니다. 즉, $.getJSON
명령을 호출하여 task를 불러오고, $.post
명령을 이용하여 새로운 task를 추가하게 됩니다.
$('#new_task').submit(function(e) { $.post('/tasks', $(this).serialize(), addTask); this.reset(); e.preventDefault(); }); $.getJSON('/tasks', function(tasks) { $.each(tasks, function() { addTask(this); }); });
여기서는 이와 같이 코드를 간단하게 유지했지만 Backbone.js와 같은 클라이언트 사이드 프레임워크를 이용할 수도 있습니다.
추가 데이터포맷을 처리하기
다음으로는 Rails::API 컨트롤러에서 언급하지 않았던 내용을 다룰 것입니다. API를 이용하면 다른 포맷에도 반응을 할 수 있도록, JSON 뿐만아니라 XML 포맷으로 tasks 목록을 보여줄 수 있습니다. 일반적으로는 아래와 같이 respond_to
블록을 추가하여 이러한 작업을 할 수 있습니다.
def index @tasks = Task.all respond_to do |format| format.json { render json: @tasks } format.xml { render xml: @tasks } end end
이제 http://localhost:3000/tasks.xml
주소를 방문하면 XML 데이터를 볼 수 있을 것으로 기대하지만 undefined method `respond_to’
라는 에러를 보게 될 것입니다. Rails::API 컨트롤러에는 많은 기능들이 생략되어 있어서 훨씬 가볍기는 하지만 필요시에는 다시 원하는 기능을 간단하게 추가할 수도 있습니다. 이를 위해서는 TasksController
를 아래와 같이 변경하여 ActionController::MimeResponds
와 같은 모듈을 포함해 주어야 합니다.
class TasksController < ApplicationController include ActionController::MimeResponds # Actions omitted end
다른 컨트롤러에서도 XML 결과물을 반환하기를 원할 경우에는 ApplicationController
에 이 모듈을 포함해 두어야 합니다. 이제 웹페이지를 다시 로드하면 task를 XML 포맷으로 볼 수 있게 될 것입니다.
Rails::API README 파일을 보면 인클루드할 수 있는 모듈 목록을 볼 수 있습니다. ActionController::MimeResponds
외에도 translations, Basic HttpAuthentication 모듈 등을 인클루드할 수 있습니다. 이것은 모듈의 전체 목록은 아닙니다. 소스코드를 보면 Rails::API controller에 포함된 것과 ActionController::Base5에 포함된 것을 비교해 볼 수 있다. ActionController::Base의 모듈을 모두 인클루드하게 되면 기능이 중복되게 될 것이다. 따라서 이와 같이 필요한 모듈만 정확하게 선별하여 인클루드하는 것이 좋습니다.
Rails::API README 파일에는 이와 같이 포함할 수 있는 모듈 목록이 있습니다.
그러나, 때로는 하나의 컨트롤러에 하나 이상의 모듈을 포함해야 할 경우가 있습니다. 예를 들어, 쿠키 기능을 추가하고자 할 경우에는 하나의 모듈뿐만 아니라 Rack 미들웨어도 추가해 주어야 할 필요가 있습니다. 이것은 중요한 의미를 가지는데, 레일스 API가 해당 컨트롤러 뿐만 아니라 Rack 미들웨어 스택까지도 가볍게 만들어 준다는 것입니다. 현재 작업하고 있는 API 어플리케이션으로 가서 rake middleware
명령을 실행하면 사용하고 있는 미들웨어의 목록을 알 수 있을 것입니다. 이것은 일반 레일스 어플리케이션에 대한 목록보다는 더 작은 것을 입니다.
$ 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
여기에는 미들웨어 5개가 빠져 있을 것입니다. 일반 레일스 어플리케이션과 작업 중인 레일스 API 어플리케이션을 비교해 볼 때 아래와 같은 미들웨어 항목들이 빠져 있다는 것을 알게 될 것입니다.
> use Rack::MethodOverride > use ActionDispatch::Cookies > use ActionDispatch::Session::CookieStore > use ActionDispatch::Flash > use ActionDispatch::BestStandardsSupport
이 미들웨어들의 자세한 기능을 알고자 할 경우에는 319번 연제를 살펴보기 바랍니다.
다시 이러한 기능(쿠키지원 등과 같은)을 추가하고자 할 경우에는 해당 미들웨어를 추가하고 관련 컨트롤러 모듈을 포함할 필요가 있을 것입니다. 이를 위해서 아래와 같이 어플리케이션 설정 파일에 해당 미들웨어를 추가할 것입니다.
module Todo class Application < Rails::Application config.middleware.insert_after ActiveRecord::QueryCache, ActionDispatch::Cookies # Rest of class omitted end end
미들웨어 전체 목록에 있는 것과 동일한 순서로 미들웨어를 유지하는 것이 좋은데, 여기서는 insert_after
메소드를 이용하여 전체 목록의 순서와 같이 Cookies 미들웨어를 QueryCache
다음에 추가하였습니다. 이제 TasksController
에서 쿠키 기능을 추가하기 위해 Cookies
모듈을 포함시킬 수 있습니다.
class TasksController < ApplicationController include ActionController::MimeResponds include ActionController::Cookies # Actions omitted end
추가한 미들웨어를 반영하기 위해서 어플리케이션을 다시 시동할 필요가 있습니다. 이제 웹페이지를 다시 로드하게 되면 help_method
가 정의되지 않았다는 에러 메시지가 나타날 것입니다.
이것은 매우 중요한 점을 시사하는데, 어떤 모듈들은 다른 모듈에 의존성을 가질 수 있다는 것입니다. 이 경우에는, Cookies
모듈이 Helpers
모듈에 의존성을 가지므로 이 모듈도 함께 추가해 주어야 합니다.
class TasksController < ApplicationController include ActionController::MimeResponds include ActionController::Helpers include ActionController::Cookies # Actions omitted end
웹페이지를 다시 로드하면 이제 제대로 동작하게 될 것입니다. 이제 일반 레일스 어플리케이션 처럼 동일하게 쿠키를 사용할 수 있게 되었습니다.
JBuilder와 RABL을 이용하여 JSON 생성하기
다음로는 JSON API를 작성하는데 도움을 줄 수 있는 다른 젬들에 대해서 언급할 것입니다. 이전 연제에서 Jbuilder와 RABL에 대해서 다루었습니다. 이 젬들은 뷰 템플릿을 이용하여 JSON을 렌더링할 것입니다. 이것은 오히려 Rails::API를 이용할 때의 장점에 반하는 것이지만 원할 경우 사용할 수 있습니다. 여기서는 RABL을 이용하여 설명할 것입니다. Gemfile에 이 점을 추가하고 bundle install 합니다.
gem 'rabl'
/app
디렉토리에는 views 디렉토리가 없기 때문에 RABL이 뷰 템플릿을 만들 수 있도록 views 디렉토리를 생성할 것입니다. views 디렉토리 아래에 tasks
디렉토리를 만들어서 index.json.rabl
파일을 추가합니다. 이 파일에는 RABL이 id
와 name
속성을 가지는 collection으로 tasks를 렌더링하도록 코드를 추가해 줍니다.
collection @tasks attributes :id, :name
이 컨트롤러에서는 JSON 렌더링 동작을 변경해서 이 템플릿이 렌더링되도록 합니다. 일반 레일스 어플리케이션에서는 render
메소드 호출을 완전히 제거하더라도 묵시적으로 해당 템플릿을 렌더링할 수 있을 것입니다. 그러나, Rails::API 어플리케이션에서는 이러한 렌더링 작업을 하기위해서는 명시적으로 메소드를 기술해 주어야 하지만 여기서는 render
메소드만 호출하여도 현재 액션에 해당하는 템플릿 파일을 렌더링하게 될 것입니다.
def index @tasks = Task.all respond_to do |format| format.json { render } format.xml { render xml: @tasks } end end
다시 어플리케이션을 재시작하고 tasks.json
경로를 방문하게 되면 RABL이 통해서 렌더링한 JSON 결과물을 보게 될 것입니다.
그러나 또 다시 묵시적인 렌더링 작업을 원할 경우에는 ImplicitRender
모듈을 인클루드해서 더 이상 render
메소드를 호출하지 않아도 됩니다.. 이 때 XML 템플릿을 추가할 경우 해당 포맷이 인지될 경우 자동으로 해당 템플릿이 렌더링될 것이기 때문에 더 이상 respond_to
메소드가 필요없게 되는 것입니다.
이렇게 되면 묵시적인 렌더링 작업이 가능하게 되므로 점점 더 일반적인 레일스 어플리케이션의 모습을 띠게 됩니다. 그러나, 이와 같이 하여 모든 기능을 다시 추가하게 될 경우 `Rails::API`를 이용하는 장점에 대해서 의구심을 가지게 될 것입니다. 아마도 이런 경우라면 일반적인 레일스 어플리케이션을 작성하는 것이 더 낫을지도 모르겠습니다. 선택권은 어플리케이션의 요구사항에 달려 있게 될 것이고 사용하고자 하는 Rack middleware의 사용범위에 좌우될 것입니다.
JSON API를 작성하고자 한다면 먼저 README 파일의 “Why use Rails for JSON APIs?” 를 읽어 볼 필요가 있습니다. 여기에는 외부에서 HTML을 렌더링하기 위해 레일스가 제공해야 하는 것에 대한 자세한 내용이 있다.