I love Ruby on Rails, but one thing that bothered me a lot was how quickly the routes.rb
file can get unwieldy. When I began creating version 2 of an API I was making, this issue presented itself. Thankfully, there's an easy way to handle this with a little knowledge of how ruby can reference extended objects.
Consider the following example routes file:
Rails.application.routes.draw do
root to: redirect("https://www.example.com/show")
get "/ready", to: proc { [200, {}, [""]] }
namespace :api do
namespace :v1 do
# about 31 routes
end
namespace :v2 do
# about 36 routes
end
# v3, v4, v5 and so on
end
end
That can get extremely long and drawn out, especially if you can't drop older versions of the API for an extended period of time. We can break this up easily by utilizing some events and methods of ruby's extend
class and break these versioned routes into their own files.
Folder and File Structure
The routes.rb
file is located in the config
folder of a Rails app. Create a new folder in the same place: config/routes
. We'll store the new route extensions here.
Inside that folder, I want to name the file the same thing as the namespace that will be in the file. The first route collection I want to siphon off is :api :v1
. Name the file api_v1_routes.rb
to match our module and namespace structure.
Open the new file and create a module called ApiV1Routes
.
module ApiV1Routes
end
Extended
When you extend an object, ruby has a helpful event called extended that allows you to execute some code after the object is... extended.
module ApiV1Routes
def self.extended(router)
end
end
In the above method, when we create a class that extends this particular module, the extended event will fire with a reference to the object that is being extended. In our case, the extended method is going to receive the router
as a parameter.
Execution In Context
Now we have a module that can get a reference to the rails router when it's extended, how do we add routes to the router from here?
All objects in ruby have a method called instance_exec. This takes a block and executes the block within the context of the caller. In our ApiV1Routes
module, this looks like the following.
module ApiV1Routes
def self.extended(router)
router.instance_exec do
# add routes
namespace :api do
namespace :v1 do
# 31 v1 routes
end
end
end
end
end
According to the documentation for instance_exec
, the self
is set to the calling object so we have access to all the object's instance variables and methods.
In order to set the context, the variable self is set to obj while the code is executing, giving the code access to obj’s instance variables.
The routes are being added as if we were within the routes.rb
file itself!
Tying it all together.
Now that we've created the module we need rails to load these route modules, otherwise we won't be able to reference them in our code.
Open the application.rb
file, which should also be located in the config
folder. Add the following line of code:
# Load split router files
config.autoload_paths += %W(#{config.root}/config/routes)
This tells rails to load the files in the routes folder, since config/routes
isn't a default that rails will autoload for you.
Back in the routes.rb
file, at the top add extend ApiV1Routes
before everything else in the do
block.
Rails.application.routes.draw do
extend ApiV1Routes
extend ApiV2Routes
# all our other routes
end
That's it! Any routes you've added this way should now be able to be used! Repeat this for any clearly defined route structures you'd like to separate, then add the extend ModuleName
to the routes.rb
file.
Top comments (5)
Here's an alternate way.
No need to use extends or modularize your routes.
Thanks, used your way.
P.S. In previous project used the next receipt = mattboldt.com/separate-rails-route... but your style is more clean, imho.
Dude that's great! I will re-evaluate my approach knowing this. :)
FYI : Now rails v6.1 support out of the box,
guides.rubyonrails.org/routing.htm...
Thanks!