Railsã使ã£ãRESTfulãªAPIã®ä½ãæ¹
ãµã¼ãã¼ã¨é£æºããiPhoneã¢ããªãããããå人ã§ãä½ããããªã¨æã£ãã®ã§ãã¨ããããéçºãããã¨ã®ããæ¹æ³ãã¾ã¨ãã¦ã¿ã¾ãããä»åã¯rails 2.3.8, ruby 1.8.7, nokogiri 1.4.3.1ãªç°å¢ã§ä½ã£ã¦ãã¾ãã
ç°¡åãªä»æ§
ã¿ã¹ã¯ãCRUDã§ããã ãã®åç´ãªAPIãä½ãã¾ãã
ä¸è¨ã®ã¡ã½ãããç¨æãã¦ãXMLã¨JSONã®ãã©ã¼ãããã«å¯¾å¿ãã¾ãã
ã | method | URI | params | ãã®ä» |
æ¤ç´¢ | GET | /api/search.format | kw=æ¤ç´¢ã¯ã¼ã | kwããªãå ´åã¯å ¨ä»¶è¿ã |
表示 | GET | /api/tasks/id.format | ã | |
ç»é² | POST | /api/tasks/id.format | name=ã¿ã¹ã¯ | ã |
ç·¨é | PUT | /api/tasks/id.format | name=ã¿ã¹ã¯ | ã |
åé¤ | DELETE | /api/tasks/id | ã | ã¬ã¹ãã³ã¹ãããã®ã¿è¿ã |
éçº
ã¾ãã¯ããã¸ã§ã¯ãã¨å¿ è¦ãªãã¡ã¤ã«ãä½ãã¾ãã
$ rails api -d mysql $ cd api $ script/generate controller api/tasks $ script/generate model task name:string $ rake db:create $ rake db:migrate $ script/console Loading development environment (Rails 2.3.8) ruby-1.8.7-p302 > Task => Task(id: integer, name: string, created_at: datetime, updated_at: datetime)
次ã«ä»æ§ã«æ²¿ã£ã¦ã«ã¼ãã£ã³ã°ãè¨å®ãã¾ãã(/config/routes.rb)
.:formatã¯xml, jsonãªã©è¤æ°ã®ãã©ã¼ãããã«å¯¾å¿ããããã®è¨è¿°ã§ãã
ActionController::Routing::Routes.draw do |map| ... map.namespace :api do |api| api.connect 'search.:format', :controller => :tasks, :action => :search, :conditions => { :method => :get } api.connect 'tasks/:id.:format', :controller => :tasks, :action => :show, :id => /\d+/, :conditions => { :method => :get } api.connect 'tasks.:format', :controller => :tasks, :action => :create, :conditions => { :method => :post } api.connect 'tasks/:id.:format', :controller => :tasks, :action => :update, :id => /\d+/, :conditions => { :method => :put } api.connect 'tasks/:id', :controller => :tasks, :action => :delete, :conditions => { :method => :delete } ... end
æ£ããè¨å®ã§ãã¦ãããã¯ä¸è¨ã®ã³ãã³ãã§ç¢ºèªã§ãã¾ãã
$ rake routes (in /path/to/project) GET /api/search(.:format) {:action=>"search", :controller=>"api/tasks"} GET /api/tasks/:id(.:format) {:action=>"show", :controller=>"api/tasks"} POST /api/tasks.format {:action=>"create", :controller=>"api/tasks"} PUT /api/tasks/:id(.:format) {:action=>"update", :controller=>"api/tasks"} DELETE /api/tasks/:id {:action=>"delete", :controller=>"api/tasks"}
ç¶ãã¦APIã®å®è£
ã§ãã
ãããªã«å¤ããªãã®ã§å
¨é¨ä¸æ°ã«ã
class Api::TasksController < ApplicationController require 'nokogiri' skip_before_filter :verify_authenticity_token # allow CSRF # GET /search.:format def search tasks = Task.find(:all, :conditions => ["name like ?", "%#{params[:kw]}%"]) respond_to do |format| format.xml { render :xml => create_xml(tasks) } format.json { render :json => create_json(tasks) } end end # GET /tasks/id.:format def show task = Task.find_by_id(params[:id]) (render_error(404, "resource not found") and return) unless task respond_to do |format| format.xml { render :xml => create_xml(task) } format.json { render :json => create_json(task) } end end # POST /tasks/id.:format def create (render_error(400, "missing name param") unless params[:name]) and return task = Task.create({ :name => params[:name] }) respond_to do |format| format.xml { render :xml => create_xml(task), :status => 201 } format.json { render :json => create_json(task), :status => 201 } end end # PUT /tasks/id.:format def update task = Task.find_by_id(params[:id]) render_error(404, "resource not found") and return unless task task.name = params[:name] task.save! respond_to do |format| format.xml { render :xml => create_xml(task) } format.json { render :json => create_json(task) } end end # DELETE /tasks/id def delete task = Task.find_by_id(params[:id]) render_error(404, "resource not found") and return unless task task.delete head 200 end private def render_error(status, msg) render :text => msg, :status => status end def create_xml(tasks) tasks = [tasks] unless tasks.class == Array xml = build_xml do |xml| xml.tasks { tasks.each do |task| xml.task(:id => task.id) { xml.name(task.name) xml.created_at(task.created_at) } end } end end def create_json(tasks) tasks = [tasks] unless tasks.class == Array tasks = tasks.inject([]){ |arr, task| arr << { :id => task.id, :name => task.name, :created_at => task.created_at }; arr } { :tasks => tasks }.to_json end def build_xml(&block) builder = Nokogiri::XML::Builder.new(:encoding => 'UTF-8') { |xml| yield(xml) } builder.to_xml end end
ããã¸ã§ã¯ããä½æããã¨ããã©ã«ãã§CSRF対çã®ããã®ã³ã¼ããå ¥ã£ã¦ããã®ã§ããããã®ã¾ã¾ã ã¨POSTãªã¯ã¨ã¹ãã§ãã¼ã¿ã®ç»é²ãã§ããªãã®ã§ãbefore_filterã§ç¡å¹ã«ãã¦ãã¾ãã
XMLã®æ´å½¢ã«ã¤ãã¦ã¯ãActiveRecordã®to_xmlãã®ã¾ã¾ã§ã¯ä½¿ãã«ããã®ã§ãNokogiriãå©ç¨ãã¦ãã¾ãã
åä½ç¢ºèª
æå¾ã«ãscript/serverã§APIãµã¼ãã¼ãèµ·åãã¦ãæ£ããæ¥ç¶ã§ããã確èªãã¾ããããã§ã¯curlãå©ç¨ãã¦ãã¾ãããFirefoxã ã¨RESTTestã¨ããã¢ããªã³ã便å©ã§ãã
ç»é² [POST] /api/tasks/id.format
$ curl -X POST -d "name=task1" http://localhost:3000/api/tasks.xml <?xml version="1.0" encoding="UTF-8"?> <tasks> <task id="1"> <name>task1</name> <created_at>2010-09-11 07:13:26 UTC</created_at> </task> </tasks>
$ curl -X POST -d "name=task1" http://localhost:3000/api/tasks.json {"tasks":[{"created_at":"2010-09-11T07:14:17Z","name":"task1","id":2}]}
ãããªæãã§ãã¼ã¿ãè¿ã£ã¦ãã¾ãã
ãã®ä»ã®ã¡ã½ããã以ä¸ã®ã³ãã³ãã§ç¢ºèªã§ãã¾ãã
ç·¨é [PUT] /api/tasks/id.format
$ curl -X PUT -d "name=task1 modified" http://localhost:3000/api/tasks/1.xml $ curl -X PUT -d "name=task1 modified" http://localhost:3000/api/tasks/1.json
åå¾ [GET] /api/tasks/id.format
$ curl -X GET http://localhost:3000/api/tasks/1.xml $ curl -X GET http://localhost:3000/api/tasks/1.json
æ¤ç´¢ [GET] /api/search.format
$ curl -X GET -d "kw=task" http://localhost:3000/api/search.xml $ curl -X GET -d "kw=task" http://localhost:3000/api/search.json
åé¤ [DELETE] /api/tasks/id
$ curl -X DELETE http://localhost:3000/api/tasks/1
å®æï¼
ç´°ããã¨ã©ã¼å¦çãå ¥ã£ã¦ãªãã£ããããã©ã¡ã¼ã¿ã®è¾ºããå¾®å¦ã ã£ãããã¾ãããããã辺ã調æ´ããã°ç«æ´¾ãªAPIã®å®æã§ãã
å®éã®ãµã¼ãã¹ã§ä½¿ããããã«ãããã¨ããã¨ããªã½ã¼ã¹åä½ã§å¥éºã«åå²ã§ããªãã£ããããã®ã§è¨è¨ãçµæ§é£ããã®ã§ãããã¹ãã¼ããã©ã³ã¢ããªãªã©ããå©ç¨ã§ããAPIãèããå ´åã対象ã¨ãªããªã½ã¼ã¹ãå°ãªããè¨è¨ãããã¾ã§è¤éã«ã¯ãªããªããããåå使ããããªã¼ã¨æãã¾ãã