RailsでAPIを書いたときの雑な感想

某インターンのメンターをしながら, メンターたちと空いてる時間を使ってネイティブアプリを書いていた. その時バックエンドをRailsで書いて思ったことを夏休みの感想文としてまとめてみた.

ライブラリの選定

Grape

RailsでAPIといえば, Grapeというライブラリが有名だけど, 特にRailsのデフォルトのコントローラで困ることが少なかったのと, Railsとの記法の違いっぽいところがあって, 導入しなかった.

ただいま考えてみれば, パラメータのバリデーションや, (どう管理するのかしらんけど)Cookieをサポートしていたり, エラーハンドリングもいい感じだし, 導入してみたら楽だったかもしれない.

各アクションにdescを書いておけば, ドキュメントの自動生成とかできたりするし. 実はとても便利かもしれない.(反省終わり)

Rabl

GrapeのかわりにRablというJSONとかの記述を楽にするDSLを提供するテンプレートエンジンを導入した. 単純にRailsのrender :jsonとかで出力してしまってもいいのだが, コントローラでJSONの構造を構築するのはスマートではないし, InstagramやTwitterのAPIでは, すべてのAPIレスポンスにEnvelopeと呼ばれる, APIのコールにステータスコードやメッセージをのせるケースが多く, そういうのに対応させようと思うとRablがいいかな, と思って導入した.

例えば, ビューで

# app/views/posts/index.rabl
collection @posts
attributes :id, :title, :subject
child(:user) { attributes :full_name }
node(:read) { |post| post.read_by?(@user) }

って書くと

[{  "post" :
  {
    "id" : 5, "title": "...", "subject": "...",
    "user" : { "full_name" : "..." },
    "read" : true
  }
}]

って出力される(公式丸パクリ). 便利.

RailsでAPIを書くときの知見

Rails+RablでAPI書いてたまった知見を書いていく.

CSRF対策

APIリクエストの場合, CSRF対策が特に必要ないので, ApplicationControllerに以下のように記述することで, CSRFを回避する.

skip_before_action :verify_authenticity_token, if: :json_request?

protected

def json_request?
  request.format.json?
end

Strong Parameters

RailsのForm+Strong Parametersだと, よく以下のような記述を見る.

def article_params
   params.require(:article).permit(:headline, :content)
end

ただAPIのクエリは, {article:{ headline: "hoge", content: "piyo" }}みたいなネストした感じでクエリ送ることってあんまりないと思う. ということでStrong Parametersを利用するために以下のように書いた.

def article_params
  ActionController::Parameters.new(params).permit(:headline, :content)
end

基底コントローラの活用

RailsでAPI書いてると, 多分/api以下はデフォルトでJSONで返したいニーズとかあると思う. そういう場合は, ApplicationControllerを継承したApi::ApplicationControllerとか定義して/app/controllers/api/application_controller.rbとし, そこにrespond_to :jsonとか, default_format :jsonとかしておくと何かと便利.

不満点とか

不便だと思った点とか上げていく.

例外の扱い

Railsのコントローラ内で投げられる例外は, すべてそのステータスコードをもってほしいと思った. またコントローラ内でモデルのCRUDを行って失敗した時は, 直ちに例外を投げるように実装を行うべきだと思った.

明示的にキャッチしなかった例外は, ApplicationControllerでハンドルし, 例外が持っているメッセージとステータスコードを出力することで, 一貫したエラー処理が行えるし, 各コントローラでは基本例外を投げるので, 分岐が減り, 読みやすいコントローラが書けるのではと思った.

あと, No route errorは例外でキャッチしたい.

RablのLayoutsがうまく動かない

最初に書いたEnvelopeの対応はRablのLayoutsを使うと綺麗にかけて良さそうだけど, 以下のサンプルのソースを書いてもうまくいかなかった. 具体的にはyieldの部分がnilになってしまった.

node(:status) { response.status }
node(:error)  { @error.to_json.html_safe }

node(:result) do
  case content_type
  when :json
    Yajl::Parser.parse(yield)
  when :xml
    Nori.new.parse(yield)
  end
end

直し方募集中.

まとめ

Railsで雑にAPIを納品した時の感想として, 概ねRailsでいい感じにかけていいけど, 多少不満とかあったのでまとめてみた. たぶんGrape導入したら解決しそうだし皆さんGrape使いましょう(完).