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を更新しないと再読みこみしないのがどうしたもんかなーという感じではある。