Our config/routes.rb file was getting a bit long, so I wanted to reorganize it and split it up into a few different files.

I found a few blog posts with some solutions, but I didn’t really like their approach. They defined a draw method that would read a Ruby file and call instance_eval with the string. This works fine, but it felt a bit wrong. Instead of using instance_eval, I decided to organize my routes with some modules. This approach is a bit more verbose, but I also think it’s a bit cleaner.

First, create a new initializer at config/initializers/routes.rb to set up a DSL helper module, and reload routes during development:

# Define a module with a #draw_routes method (e.g. `YourRoutesModule`).
# Call `extend ActionDispatch::Routing::Concern` inside that module.
# Then call `extend YourRoutesModule` inside the Rails.application.routes.draw block.
# `#draw_routes` will be called in the current context (namespace, constraints, etc.)
module ActionDispatch
  module Routing
    module Concern
      def extended(mod)
        mod.instance_exec do
          draw_routes
        end
      end
    end
  end
end

# Reload routes during development if a file changes in config/routes/*
Rails.application.configure do
  route_reload_paths = {
    Rails.root.join('config', 'routes').to_s => ['rb'],
  }
  route_reloader = config.file_watcher.new([], route_reload_paths) do
    reload_routes!
  end
  reloaders << route_reloader
  config.to_prepare { route_reloader.execute_if_updated }
end

Thanks to Robert Mosolgo for this awesome article: Watching Files During Rails Development (web.archive.org link). The reloading code was taken from these lines in react-rails.

Create a config/routes/ directory. Split up your routes into different modules, such as Routes::API, Routes::Resources, Routes::Webhooks, etc.

Here is an example for config/routes/resources.rb:

module Routes
  module Resources
    extend ActionDispatch::Routing::Concern

    def draw_routes
      resources :posts do
        resources :comments
      end
    end
  end
end

Create a directory for some helpers, at config/routes/helpers/. I used a helper module to define shared constraints and some other convenience methods. Here is an example for config/routes/helpers/constraints.rb:

module Routes
  module Helpers
    module Constraints
      def with_app_subdomain
        constraints(host: APP_SUBDOMAIN) do
          yield
        end
      end
    end
  end
end

Here’s how you can put it all together in config/routes.rb:

Dir.glob(Rails.root.join('config', 'routes', '**', '*.rb')).each { |f| load f }

module Routes
  Rails.application.routes.draw do
    extend Helpers::Constraints
    extend Authentication

    with_app_subdomain do
      extend API
      extend Resources
      extend Webhooks
    end

    extend StaticPages
    extend ErrorPages

    root to: 'home#index'
  end
end

Is there something like this in Rails?

Rails used to have a way to load routes from files, but the commit was reverted. DHH tried it and said that it made things more opaque, and he didn’t want to encourage it.

I personally think that it’s a great idea to organize your routes in separate files. It makes it much easier to add a new route in the right place, instead of scrolling through a huge routes.rb.

Note that you can also DRY up your routes by using Routing Concerns.

Thanks to James Kiesel, who suggested using modules and extend instead of instance_eval.