- 前記事: Ruby: 高速/高性能ルーティングエンジンgem「Roda」README: 前編(翻訳)
- 次記事: Ruby: 高速/高性能ルーティングエンジンgem「Roda」README: 後編(翻訳)
概要
MITライセンスに基いて翻訳・公開いたします。
- リポジトリ: jeremyevans/roda
- 原文更新日: 2018/02/01
- 著者: Jeremy Evans
- サイト: http://roda.jeremyevans.net/
- API: http://roda.jeremyevans.net/rdoc/index.html
roda.jeremyevans.net/より
長いので3本に分割します。
本記事では、原則としてroutesやroutingは「ルーティング」、rootは「ルート」と表記します。
Ruby: 高速/高性能ルーティングエンジンgem「Roda」README: 中編(翻訳)
オプションのセグメント
Rodaではオプションのセグメントをさまざまな方法で扱えます。たとえば、/items/123と/items/123/456のどちらも受け付けたいとしましょう(123はitemのid、456は何らかのオプションデータ)。
最も単純な方法は、共有したブランチ(branch: 枝)で2つの独立したルーティングとして扱うことです。
r.on "items", Integer do |item_id|
# ブランチで使う共有コードがここにあるとする
# /items/123/456
r.is Integer do |optional_data|
end
# /items/123
r.is do
end
end
これは多くのケースで使えますが、これをオプションのセグメントを持つ1つのルート(route)としてどうしても扱いたいということもあります。シンプルな方法の1つとして、オプションセグメントの代わりにパラメータを使う手があります(+/items/123?opt=456+など)。
r.is "items", Integer do |item_id|
optional_data = r.params['opt'].to_s
end
しかし、オプションセグメントをどうしても使いたい場合、マッチャーを使う方法がほかにいくつもあります。そのひとつが、最後の要素がtrueの配列マッチャーを使うことです。
r.is "items", Integer, [String, true] do |item_id, optional_data|
end
技術的には、オプションセグメントが渡されなかった場合、引数を(2つではなく)1つだけyieldする点にご注意ください。
他の方法として、正規表現で実装する手もあります。
r.is "items", /(\d+)(?:\/(\d+))?/ do |item_id, optional_data|
end
マッチブロックやルートブロックからの戻り値
response.writeを直接呼んだことでレスポンスのbodyが既に書き込まれている場合、マッチブロック(match block)やルートブロック(route block)の戻り値はすべて無視されます。
レスポンスのbodyがまだ書き込まれていない場合、マッチブロックやルートブロックの戻り値がinspectされます。
String- レスポンスのbodyとして使われる
nil、false- 無視される
- その他すべて
- エラーを
raise
プラグインでは、マッチブロックの戻り値やルートブロックの戻り値の追加をサポートします。JSONプラグインを例に取ると、マッチブロックやルートブロックで配列やハッシュを返せるようになり、これらを直接JSONに変換してレスポンスのbodyとして使えます。
ステータスコード
レスポンスを確定する段階で、ステータスコードが手動で設定されておらず、レスポンスに何らかの書き込みが行われている場合、レスポンスのステータスコードには200が使われます。それ以外の場合のステータスコードは404になります。これによって「驚き最小の原則」を実現します。アクションがどこでも扱われていない場合のレスポンスは404が前提になります。
レスポンスのstatus属性を使って、いつでも手動でステータスコードを設定できます。
route do |r|
r.get "hello" do
response.status = 200
end
end
リダイレクトの場合、レスポンスのステータスコードはデフォルトで302になります。このステータスコードはr.redirectに渡す2番目の引数で変更できます。
route do |r|
r.get "hello" do
r.redirect "/other", 301 # use 301 Moved Permanently
end
end
verb(動詞)メソッド
上述のように、RodaにはHTTPリクエストメソッドを元にマッチングを行うr.getメソッドとr.postメソッドがあります。これ以外のHTTPリクエストメソッドとマッチさせたい場合は、all_verbsプラグインを使います。
引数なしで呼ぶと、リクエストに適切なメソッドがある限りマッチしたと判断されます。
r.get do end
上はあらゆるGETリクエストにマッチします。
r.post do end
上はあらゆるPOSTリクエストにマッチします。
メソッドに引数を渡す場合は、「リクエストメソッドとのマッチ」と「すべての引数とのマッチ」と「パスが完全に引数とマッチ」がすべて満たされる場合にのみマッチと判断されます。
r.post "" do end
上は、現在のパスが/のPOSTリクエストにだけマッチします。
r.get "a/b" do end
上は、現在のパスが/a/bのGETリクエストにだけマッチします。
このように振る舞いを変えてある理由は、引数をまったく渡さないということは「おそらく現在のパスと完全一致するかどうかのテストまでしたくなんかないだろう」と考えたためです。「いやいや、それテストしたいから」というのであれば、引数にtrueを渡してください。
r.on "foo" do
r.get true do #「GET /foo」にマッチ、「GET /foo/.*」にはマッチしない
end
end
リクエストメソッドにマッチさせ、かつリクエストパスについては部分マッチだけ行いたい場合は、r.onで:methodハッシュマッチャーを使う必要があります。
r.on "foo", method: :get do # Matches GET /foo(/.*)?
end
ルート(root)メソッド
上述のように、r.rootをマッチメソッドとして使うこともできます。このメソッドは、現在のパスが/であるGETリクエストにマッチします。r.rootはr.get ""と似ていますが、/の指定に余分な場所を使わないで済む点が異なります。
他のマッチメソッドと異なり、r.rootは引数を取りません。
r.rootは、パスが空の場合にはマッチしない点にご注意ください。その場合はr.get trueを用いるべきです。空のパスと/の両方にマッチさせたい場合は、r.get ["", true]とするか、slash_path_emptyプラグインを使います。
r.rootはGETリクエストでないとマッチしない点にご注意ください。POST /リクエストを扱うには、r.post ''を使います。
リクエストとレスポンス
リクエストオブジェクトはrouteブロックにyieldされますが、requestメソッドで取得することもできます。同様に、レスポンスオブジェクトはresponseメソッドで取得できます。
リクエストオブジェクトは、Rack::Requestのサブクラスのインスタンスにいくつかのメソッドを追加したものです。
リクエストオブジェクトやレスポンスオブジェクトを別のモジュールで拡張【チェック】したい場合は、module_includeプラグインを利用できます。
「汚染」について
Rodaでは、さまざまな手をつくしてrouteブロックのスコープの汚染を回避しようとしています。このため、Rodaがアプリのコードで名前空間の問題を引き起こす可能性は小さいはずです。Rodaでは以下を含む対策を取っています。
routeブロックのスコープでデフォルトで定義されるインスタンス変数は、@_requestと@_responseしかありません。Rodaに付属するプラグインがrouteブロックのスコープで用いるインスタンス変数は、すべて冒頭にアンダースコア_が追加されます。- (
Objectのデフォルトメソッドの他に)定義されるメソッドは、次のものしかありません:call、env、opts、request、response、session - Roda名前空間内の定数は、すべて
Rodaで始まります(例:Roda::RodaRequest)。
コンポジション
(別のRodaアプリを含む)任意のRackアプリとそのミドルウェアをRodaアプリ内部にマウントしてr.runを利用できます。
class API < Roda
route do |r|
r.is do
# ...
end
end
end
class App < Roda
route do |r|
r.on "api" do
r.run API
end
end
end
run App.app
これは/apiで始まる任意のパスを取ってAPIに送信します。この例におけるAPIはRodaアプリのことですが、これをSinatraやRailsといった他のRackアプリにするのも簡単です。
r.runを使うと、Rodaは指定のRackアプリを呼び出します(ここではAPI)。そのRackアプリが返すものはすべて、現在のアプリのレスポンスとして返されます。
振り分け先のRackアプリがたくさんあり、かつリクエストパスのプレフィックスを元に振り分けたいのであれば、multi_runプラグインを調べてください。
multi_routeプラグイン
メインのrouteブロックをブランチごとに分けたいだけなら、multi_routeプラグインを用いるべきです。これは現在のrouteブロックのスコープを維持します。
class App < Roda
plugin :multi_route
route "api" do |r|
r.is do
# ...
end
end
route do |r|
r.on "api" do
r.route "api"
end
end
end
run App.app
これにより、メインのrouteブロックに1つ以上のインスタンス変数を設定して、api routeブロック内部でもそれらのインスタンス変数にアクセスできるようになります。
テスト
Rodaは、Rack::TestやCapybaraを用いて実に簡単にテストできます。デフォルトのRakeタスクはRodaのspecを実行します。
設定
Rodaアプリごとの設定は、optsハッシュ内の設定に保存できます。この設定はサブクラスに継承されます。
Roda.opts[:layout] = "guest"
class Users < Roda; end
class Admin < Roda
opts[:layout] = "admin"
end
Users.opts[:layout] # => 'guest'
Admin.opts[:layout] # => 'admin'
便利そうなものがあれば、何でも自由に保存できます。ただしサブクラス化した場合、Rodaは設定の「浅いコピー」(shallow clone)しか行わないのでご注意ください。
ネストした構造を保存してそれらをサブクラス内で変更するのであれば、「自己責任」でRoda.inherited(superを呼べるようにする)内のネストした構造をdupしてください。サブクラス化後に親クラスを変更してもサブクラスに影響しないように、およびその逆の影響も生じないようにするために、これは必ず行うべきです。
Rodaに付属するプラグインはこれらの設定をfreezeし、プラグインの再読み込みによる設定変更だけを許可します。外部プラグインもこのアプローチに従ってください。
以下のオプションは、デフォルトのライブラリやさまざまなプラグインで考慮されます。
:add_script_name- リクエストの
SCRIPT_NAMEをパスの前に追加します。これは、アプリを別のアプリの下のパスとしてマウントする場合に便利です。 :freeze_middleware- ラックアプリのビルド時にすべてのミドルウェアをfreezeするかどうかを指定します。
:root- アプリのルート(root)パスを設定します。デフォルトは、プロセスの現在のワーキングディレクトリになります。
個別のプラグインがサポートするこの他のオプションがある場合は、プラグインのドキュメントで言及します。
レンダリング
Rodaには、レンダリングテンプレートのヘルパーを提供するrenderプラグインが付属します。このプラグインで用いられているTiltというgemは、さまざまなテンプレートエンジンとのインターフェイスとなります。デフォルトではerbエンジンが用いられます。
このプラグインで使うには、Tilt gemと、使いたいテンプレートエンジンをインストールする必要があります。
このプラグインは、テンプレート出力用のrenderメソッドとviewメソッドを追加します。デフォルトでは、viewはデフォルトのレイアウトテンプレート内のテンプレートを出力し、renderはテンプレートを単に出力します。
class App < Roda
plugin :render
route do |r|
@var = '1'
r.get "render" do
# views/home.erbテンプレートをレンダリングする
# このテンプレートはローカル変数のコンテンツと同様に
# インスタンス変数@varにもアクセスできる
render("home", locals: {content: "hello, world"})
end
r.get "view" do
@var2 = '1'
# views/home.erbテンプレートをレンダリングする
# このテンプレートはインスタンス変数@varと@var2にアクセスでき、
# その出力を受け取ってviews/layout.erb内部でレンダリングする
# (これはコンテンツが挿入されるべき箇所でyieldされるはず)
view("home")
end
end
end
プラグインにハッシュを1つ渡すことで、デフォルトのレンダリングオプションをオーバーライドできます。
class App < Roda
plugin :render,
escape: true, # Erubiのエスケープサポートでerbテンプレートの出力を自動エスケープする
views: 'admin_views', # デフォルトのビューディレクトリ
layout_opts: {template: 'admin_layout', engine: 'html.erb'}, # デフォルトのレイアウトオプション
template_opts: {default_encoding: 'UTF-8'} # デフォルトのテンプレートオプション
end
セッション
Rodaはデフォルトではセッションをオンにしませんが、多くのユーザーがセッションサポートをオンにしたいと思うでしょう。最もシンプルな方法は、Rackに付属するRack::Session::Cookieミドルウェアを用いることです。
require "roda"
class App < Roda
use Rack::Session::Cookie, secret: ENV['SECRET']
end
- 前記事: Ruby: 高速/高性能ルーティングエンジンgem「Roda」README: 前編(翻訳)
- 次記事: Ruby: 高速/高性能ルーティングエンジンgem「Roda」README: 後編(翻訳)
