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.
Other Posts
instance_eval
postsRails.application.routes.draw
from multiple files
Thanks to James Kiesel, who suggested using modules
and extend
instead of instance_eval
.