Ruby on Railsでエンドポイントを変えずにformatで実行するControllerを分ける方法でちょっと悩んだので解決方法をメモしておきます。
やりたかったこと
個人のサービスでRSS用のXMLを表示するのと普通にHTMLを返却する処理を下記のようにrespond_to
で分岐するようにしていたのですが、
# routes Rails.application.routes.draw do resources :posts end # controller class PostsController < ApplicationController def index @posts = Post.all respond_to do |format| format.html format.rss { # RSS特有の処理 render layout: false } end end end
RSS用にに表示する内容とHTMLで画面に表示する内容に差が出てきてContollerを分けてくなってきたのですが、RSS用のエンドポイントは外部に公開していて変更するのは影響が大きいので、routes.rb
でformat
で実行するContorollerを分岐出来ないかと悩んでしました🤔
解決方法
routes
ではconstraints
内でlamda
を使って条件に合致するかどうかをチェックすることができるようで、これを使って解決しました💡
次のようにlambdaを使うことができます。get 'foo', constraints: lambda { |req| req.format == :json } このルーティング指定は明示的なJSONリクエストにのみ一致します。 Rails のルーティング - Railsガイド
修正したコードが下記です。constraints: lambda { |req| req.format == :rss }
でformat
がrss
のときのみにrss/posts#index
が実行するようにしてあげて、Rss::PostsController
を追加しています。respond_to
の分岐もなくなってスッキリしました👏
Rails.application.routes.draw do resources :posts, constraints: { format: :html } # `.rss`に反応しないようにformatをhtmlのみに制限 get '/posts', to: 'rss/posts#index', constraints: lambda { |req| req.format == :rss } # lambdaを使って.rssのみに制限 end # controller class PostsController < ApplicationController def index @posts = Post.all end end # controller(rss) class Rss::PostsController < ApplicationController def index @posts = Post.all # Rss特有の処理 render layout: false end end
※constraints: { format: :rss }
では制限出来ないので注意してください。Railsガイドによると下記とのことです。
get 'foo'、constraints: { format: 'json' }はGET /fooと一致します。これはデフォルトでformatがオプションであるためです。 Rails のルーティング - Railsガイド
おわりに
constraints: lambda { }
の形で実行するControllerを判定するの、何も考えずに使用すると煩雑になって大変そうですが、リクエストを元に自由に実行するContollerを決めれて便利そうですね👀