Madogiwa Blog

主に技術系の学習メモに使っていきます。

Ruby on Rails: リクエストのformatによって実行するControllerを分岐させるroutesの設定方法MEMO

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.rbformatで実行するContorollerを分岐出来ないかと悩んでしました🤔

解決方法

routesではconstraints内でlamdaを使って条件に合致するかどうかをチェックすることができるようで、これを使って解決しました💡

次のようにlambdaを使うことができます。get 'foo', constraints: lambda { |req| req.format == :json } このルーティング指定は明示的なJSONリクエストにのみ一致します。 Rails のルーティング - Railsガイド

修正したコードが下記です。constraints: lambda { |req| req.format == :rss }formatrssのときのみに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を決めれて便利そうですね👀

参考

railsguides.jp