Railsのルーティングでオプションの優先度をコントロールする記法

More than 1 year has passed since last update.


ルーティングの記法

RailsでオプションのURLを記述する際に括弧をつけるとオプションになることはよく知られていると思います。

例えば/posts/:idではidが必須扱いになりますが、/posts/(:id)ではidがオプション扱いになります。

get '/posts/:id' => 'posts#show' # idは必須

get '/posts/(:id)' => 'posts#show' # idがなくてもposts_controller#showが呼び出される

(このぐらいのルーティングならresourcesで書くことをオススメします)

この時にposts/1ではparams: {id: 1}posts/ではparams: {}となってアクションの処理を行います。


オプションが2つ以上あって複数マッチする時

しかし、オプションが2つあった時に扱いはどうなるでしょうか。

get '/posts/(category_:parent_category_id)/(category_:category_id)/:id' => 'posts#show'

この時、posts/category_1/category_2/3にアクセスするとparams: {parent_category_id: 1, category_id: 2, id: 3}と認識されます。意図通りの動作です。

カテゴリが指定されていない場合posts/3にアクセスするとparams: {id: 3}となりこれも意図通りの動作になります。

しかし、カテゴリが1階層だった場合posts/category_2/3params: {parent_category_id: 2, id: 3}と認識されます。

これは意図通りではなくて

'期待していたもの': {category_id: 2, id: 3}

'実際のもの': {parent_category_id: 2, id: 3}

だと思います。

期待していたルーティングの動作をさせるために以下の記法で書けば大丈夫でした。

(結構長い間ハマってました)

get '/posts/((category_:parent_category_id)/category_:category_id)/:id' => 'posts#show'

括弧の中に括弧で書くと優先度が変わるという話でした。よくよく考えてみると納得の動作です。

これで

posts/category_2/3にアクセスするとparams: {category_id: 2, id: 3}というパラメータが得られます。