Railsのrespond_toを苦痛なく使いこなす方法
Railsでスキャフォールドを生成すると、おなじみのrespond_toブロックが自動的に作成されます。
def destroy
@task.destroy
respond_to do |format|
format.html { redirect_to tasks_url, notice: 'Task was successfully destroyed.' }
format.json { head :no_content }
end
endしかし、よく見るとindexなど一部のアクションには、このブロックが存在していません。
# GET /tasks
# GET /tasks.json
def index
@tasks = Task.all
endこれは問題です。なぜなら、アプリがサポートしていない形式(たとえば/tasks.txt)にアクセスされると、まったく見当違いのエラーが発生してしまうからです。
ActionView::MissingTemplate (Missing template tasks/index, application/index with {:locale=>[:en], :formats=>[:text], :variants=>[], :handlers=>[:erb, :builder, :raw, :ruby, :coffee, :jbuilder]}これは正しい挙動ではありません。クライアントに対しては「リクエストされた形式はサポートしていません」と伝えるべきであり、「該当するファイルが見つかりません」と返すのは誤りです。
もしUnknownFormat例外として検出できれば、より適切なレスポンスコードを返せます。現状では、この種のエラーが他の無関係なエラーと混ざってしまい、個別にハンドリングするのが非常に困難になります。
indexアクションにrespond_toブロックを追加すれば解決できます。
# GET /tasks
# GET /tasks.json
def index
@tasks = Task.all
respond_to do |format|
format.html
format.json
end
endすると、期待どおりの例外とステータスコードが得られます。
Started GET "/tasks.txt" for 127.0.0.1 at 2014-11-03 22:05:12 -0800
Processing by TasksController#index as TEXT
Completed 406 Not Acceptable in 21ms
ActionController::UnknownFormat (ActionController::UnknownFormat):
app/controllers/tasks_controller.rb:8:in `index'ずっと良くなりました。しかしすべてのコントローラにrespond_toブロックを書き散らすのは現実的ではありません。Railsらしさに欠け、DRY原則にも反しますし、コントローラ本来の仕事への集中も妨げてしまいます。
とはいえ、不正なフォーマットへのリクエストは適切に処理したいもの。どうすればよいのでしょうか?
respond_toのショートカット構文
オブジェクトの描画に特別な処理が必要ないのであれば、ショートカットが使えます。次のように書くと:
def index
@tasks = Task.all
respond_to :html, :json
endindexに完全なrespond_toブロックを書いた場合と同じ挙動になります。これは「アクションが対応しているフォーマットの一覧」をRailsに簡潔に伝えるための記法です。アクションごとにサポートするフォーマットが異なる場合も、少ないコード量でその違いをきれいに表現できます。
コントローラレベルでフォーマットを管理する
しかし通常、コントローラ内の各アクションは同じフォーマット群を扱うことがほとんどでしょう。indexがJSONに応答するなら、newやcreateなど他のアクションも同様に対応しているはずです。そこで、コントローラ全体に作用するrespond_toが書ければ便利です:
class TasksController < ApplicationController
before_action :set_task, only: [:show, :edit, :update, :destroy]
respond_to :html, :json
# GET /tasks
# GET /tasks.json
def index
@tasks = Task.all
respond_with(@tasks)
end実際にこれで動作します。
Started GET "/tasks.txt" for 127.0.0.1 at 2014-11-03 22:17:37 -0800
Processing by TasksController#index as TEXT
Completed 406 Not Acceptable in 7ms
ActionController::UnknownFormat (ActionController::UnknownFormat):
app/controllers/tasks_controller.rb:8:in `index'まさに期待していた通りのエラーです。しかも各アクションを一つひとつ修正する必要はありませんでした。
モデルの状態によって挙動を変えたいケースもあります。たとえばcreateアクションでは、モデルが有効かどうかに応じて、リダイレクトするかフォームを再描画するかを切り替えますよね。
Railsはこれにも対応しています。ただし、respond_withで「どのオブジェクトを判定対象にするか」を教えてあげる必要があります。つまり、こう書いていたものが:
def create
@task = Task.new(task_params)
respond_to do |format|
if @task.save
format.html { redirect_to @task, notice: 'Task was successfully created.' }
format.json { render :show, status: :created, location: @task }
else
format.html { render :new }
format.json { render json: @task.errors, status: :unprocessable_entity }
end
end
endこう書けるようになります。
def create
@task = Task.new(task_params)
flash[:notice] = "Task was successfully created." if @task.save
respond_with(@task)
endこれにより、ビジネスロジックと「応答するフォーマットの定義」が分離されます。対応フォーマットは一度だけ宣言すればよく、アクションごとに繰り返す必要はありません。
responders gemの活用
Rails 4.2以降には注意点があります。respond_withは本体から除外されました。しかしresponders gemをインストールすれば、この機能を取り戻せます。さらに、このgemには他にも便利な機能が用意されています。
コントローラの先頭でresponders :flashと宣言すると、respond_with経由でフラッシュメッセージを設定できるようになります。
class TasksController < ApplicationController
responders :flashさらに便利なことに、これらのフラッシュメッセージはロケールファイル(yml)側でデフォルト値を定義しておけます。
また、Gemfileにresponders gemが含まれた状態でスキャフォールドを生成すると、ジェネレータはrespond_toではなくrespond_withを使ったコントローラを出力します。
class TasksController < ApplicationController
before_action :set_task, only: [:show, :edit, :update, :destroy]
respond_to :html, :json
def index
@tasks = Task.all
respond_with(@tasks)
end
def show
respond_with(@task)
end
# ...Rails標準のスキャフォールドよりも、かなりすっきりしたコードになります。
最後に、特定のアクションだけ追加のフォーマットに応答させたい場合は、respond_toを複数回呼び出すことも可能です。
class TasksController < ApplicationController
respond_to :html
respond_to :js, only: :create
end最後のTipsは、コメント欄にてJeroen Weeinkさんにご教授いただきました。ありがとうございます!
respond_withとrespond_to、どちらを選ぶべきか?
フォーマットごとに異なる情報を返したい場合、いくつかの選択肢があります。コントローラレベルのrespond_toとrespond_withの組み合わせは、コントローラを短く保つための優れた手段です。ただしその効果を最大限に発揮できるのは、すべてのアクションが同じフォーマットに対応し、Railsが想定する標準的な振る舞いをする場合です。
一方、一部のアクションだけ特殊な動作をさせたい場面もあります。そんなときは、1行で書けるショートカット版のrespond_toが便利です。
さらに細かい制御が必要であれば、ブロック付きの完全なrespond_toを使えば、フォーマットごとの処理を自由自在に記述できます。
どの方法を選んでも、未サポートのフォーマットへのリクエストには正しいエラーが返されるようになります。その結果、アプリケーションもクライアント側も、混乱から大きく解放されるはずです。
-
MongoDBコレクションの全キー(フィールド名)を取得する方法まとめ
この記事は2019年1月18日にObjectRocket.com/blogで公開された内容をもとにしています。 MongoDB®のコレクションに含まれるすべてのキーを把握することは、スキーマの検証、フィールド名のタイプミスのデバッグ、設定すべきでないフィールドの発見など、さまざまな場面で役立ちます。本記事では、MongoDBコレクション内の全キー名を取得する複数の方法を、状況別にわかりやすく解説します。 まず検討したい:ODMやマネージドサービスの活用 ObjectRocketをはじめとする多くのMongoDB-as-a-Service事業者は、ユーザーインターフェース(UI)上で簡単にキーを
-
MongoDBコレクション内の全キー名を取得する方法|MapReduce・Aggregation・シェルなど状況別に解説
スキーマの検証、フィールド名のタイプミスのデバッグ、設定されるべきではないフィールドの発見などを行うには、まずMongoDBコレクション内にどのようなキーが存在するのかを把握しておく必要があります。多くのMongoDB-as-a-Service事業者は、UI上で手軽にこれを実現できる機能を提供しています(ObjectRocketもそのひとつです)。また、経験豊富なMongoDBユーザーの多くは、JavaScript向けのMongooseやPython向けのMongoengineといったオブジェクトドキュメントマッパー(ODM)を導入しています。ODMを使えば、アプリケーション向けの一貫性のある