Madogiwa Blog

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

Ruby on Rails: `constraints`を使ってログイン必須等のRoutesに制約を掛けるMEMO

SidekiqのDashboard等をmountしたengineの画面をログイン必須にしたい時、特定のサブドメインのときだけアクセス可能にしたい等、Rails のルーティングのconstraintsを使うと実現できそうだったので、そのあたりをメモしておきます📝

api.rubyonrails.org

Rails のルーティングのconstraintsとは?

constraintsとはRoutingになんかしらの条件を与え、条件に合致しない場合にはActionController::RoutingErrorを発生させることができる機能です。

railsguides.jp

以下のような形でサブドメインがadminのときの場合だけアクセス化にしたり任意の制約を設定することが出来ます。

namespace :admin do
  constraints subdomain: 'admin' do
    resources :users
  end
end

Railsで必要なmatches?に応答できるオブジェクトを渡す方法があります。

また上記の通りmatches?に応答できるオブジェクトを渡して制約を設定することも出来ます。

mountしたengineの画面をログイン必須する

SidekiqのDashboard等をmountしたengineの画面をログイン必須にしたい時に、このconstraintを利用すると便利です。

以下のようにrequestsesisonからログインしている管理ユーザーのIDを取得して、そのユーザーが実際に登録されているユーザーの場合だけアクセス可能にするようなことが出来ます。

class AdminAuthConstraint
  def matches?(request)
    return false unless request.session[:admin_user_id]

    !!AdminUser.find_by(id: request.session[:admin_user_id])
  end
end

Rails.application.routes.draw do
  namespace :admin do
    require 'sidekiq/web'
    mount Sidekiq::Web, at: '/sidekiq', constraints: AdminAuthConstraint.new
  end
end

Deviseではこの辺をもっと簡単にできるauthenticatedが提供されていて以下のような形で行えるようになっています。

Rails.application.routes.draw do
    authenticated :admin_user do
      require 'sidekiq/web'
      mount Sidekiq::Web, at: '/sidekiq'
    end
  end
end

中身はwardenにアクセスしてスコープを考慮して認証済かどうかを確認し、ブロック引数があればそれを実行しているって感じみたいですね👀

def authenticated(scope = nil, block = nil)
  constraints_for(:authenticate?, scope, block) do
    yield
  end
end

def constraints_for(method_to_apply, scope = nil, block = nil)
  constraint = lambda do |request|
    request.env['warden'].send(method_to_apply, scope: scope) &&
      (block.nil? || block.call(request.env["warden"].user(scope)))
  end

  constraints(constraint) do
    yield
  end
end

github.com

参考

github.com

rails routing constraintsについて | 日々雑記