config/routes.rbを分割する
とあるサービスのconfig/routes.rbが肥大化してきたので、分割したくなっていたので、調べていた。
ググってみると色々方法があるらしいが、バージョンごとにやり方が違ったりしてげんなりする。
よく出てくるconfig.paths["config/routes"]をいじる方法は、どうもRails4からは削除されていて動かないようだ。
ということで黒魔術を使った。めでたしめでたし。
# config/routes/some_module.rb module SomeModule def self.apply(context) context.instance_exec do # 実行させたい処理を書く # resources :products do # member do # get 'short' # post 'toggle' # end # end end end end
# config.rb Dir[Rails.root.join('config/routes/*.rb')].each do |file| load file end Your::Application.routes.draw do SomeModule.apply(self) end
解説
Your::Application.routes.drawの実態は、ActionDispatch::Routing::RouteSet#drawというやつのようで、その実装は、次のようになっている。
# actionpack/lib/action_dispatch/routing/route_set.rb def draw(&block) clear! unless @disable_clear_and_finalize eval_block(block) finalize! unless @disable_clear_and_finalize nil end
#eval_blockの実装は、
def eval_block(block) if block.arity == 1 raise "You are using the old router DSL which has been removed in Rails 3.1. " << "Please check how to update your routes file at: http://www.engineyard.com/blog/2010/the-lowdown-on-routes-in-rails-3/" end mapper = Mapper.new(self) if default_scope mapper.with_default_scope(default_scope, &block) else mapper.instance_exec(&block) end end private :eval_block
なので、drawしたときに作成する&blockの実態は、instance_execのコンテキストで実行されているようだ。
with_default_scopeするケースがあるが、こちらの実装は、
# actionpack/lib/action_dispatch/routing/mapper.rb def with_default_scope(scope, &block) scope(scope) do instance_exec(&block) end end
となっていて、こちらもinstance_execになっている。
なので、コンテキストを受けとって、コンテキストに対してinstance_execしてやれば同じコンテキストで実行できるので、instance_exec以降は通常のroutesと同じことが記述できる。
開発環境で再起動しなくても読みなおせるようにloadで読みこんでいるが、config/routes.rbのtimestampを更新しないと再読みこみしないのがどうしたもんかなーという感じではある。