Railsアプリケーションでは、ルーティングで任意の条件により制約を設けることができます。 この記事では、その方法について解説します。

制御の種類

まず、アプリケーション側において、ユーザのリクエストに対する制御を行なう方法は、大別して以下の2つがあります。

1. ルーティングで制御する

ルーティングでの制御は、該当するリクエストからコントローラを隠蔽したいケースに適します。

例えば、接続元が特定のIP以外は/adminの存在を隠蔽したい場合は、ルーティングで制御します。

2. コントローラで制御する

コントローラでの制御は、レコードの状態がリクエストを受け付けるのに相応しくないケースに適します。

例えば、認証済みのユーザに管理権限がない場合はコントローラ側で制御します。

備考

もちろん、ルーティングで行おうとする制御を、コントローラで行なうことはできます。 1の例でいうと、コントローラ側で接続元IPを確認し、特定のIP以外はRoutingErrorを発生させればよいでしょう。

ただ、制御自体はルーティング/コントローラどちらでもできますが、制御の一部であるルーティングはその名のとおりルーティングの責務です。

その制御が「どちらにふさわしいか」を判断し実装することで、よりきれいな設計にすることができます。

実装方法

それでは、具体的な実装方法について説明します。

まず、#matches?というメソッドをもった制約クラスを作成します。 #matches?の返り値がtrueならリクエストを受け付け、falseなら遮断します。

config/constraints/foo_constraint.rb:

class FooConstraint
  def matches?(request)
    ...
  end
end

ルーティングでは、制約を設けたいresourcesscopeなどで、:constraintsオプションに制約クラスのインスタンスを渡します。

config/routes.rb:

resources :..., constraints: FooConstraint.new

あとは、config/constraintsディレクトリを読み込むようautoload_pathsに追加します。

config/application.rb:

config.autoload_paths += %W(
  #{config.root}/config/constraints
)

実装例

ここでは、接続元が特定IPの場合のみ/adminへのルーティングを受け入れる制約の例を示します。

config/constraints/whitelist_constraint.rb:

class WhitelistConstraint
  def initialize
    @ips = Whitelist.ips
  end

  def matches?(request)
    @ips.include?(request.remote_ip)
  end
end

config/routes.rb:

namespace :admin, constraints: WhitelistConstraint.new do
  ...
end

おわりに

ルーティングの制御を制約クラスに切り分けることで、ルーティングをきれいに実装できます。 適切なケースでは、ぜひ導入しましょう。