Routing Partials

#80 Routing Partials
5/14/2017

Summary

The routes file can grow to be unmaintainable and messy. Learn to keep things organized by extracting out blocks of routes into their own files.
4
rails routes

Resources

Gitlab Source - https://gitlab.com/gitlab-org/gitlab-ce
Episode Source - https://github.com/driftingruby/080-routing-partials


Side Note: everyone should use caution in using instance_eval and be careful to not accept user inputs (or ensure they are sanitized). I feel that it is an acceptable use case here since only the code in the routes will call this method and it is not exposed.

Summary

config/initializers/routing_draw.rbclass ActionDispatch::Routing::Mapper
  def draw(routes_name, sub_path=nil)
    if sub_path.present?
      instance_eval(File.read(Rails.root.join("config/routes/#{sub_path}/#{routes_name}.rb")))
    else
      instance_eval(File.read(Rails.root.join("config/routes/#{routes_name}.rb")))
    end
  end
end
routes.rbRails.application.routes.draw do
  draw :api
  resources :users
  root to: 'users#index'
end
config/routes/api.rbrequire 'api_constraints'

namespace :api, compress: false do
  [:v1, :v2].map { |api| draw api, :api }
end
config/routes/api/v1.rbscope module: :v1, constraints: ApiConstraints.new(version: 1, default: false) do
  resources :users
end


430345?v=3&s=64
davearonson said 13 days ago:

Just wondering about the use of .map there.  Does the mapper actually somehow use the result of each call, so that it becomes important to use .map, or is the important part more of a side-effect, so that .each would do?  If the latter, I think that would be clearer as it wouldn't raise this question, in the minds of those of us who tend to overthink things.  :-)


635114?v=3&s=64
kobaltz said 13 days ago:

Either should work since the method is calling instance_eval.


Photo
Sam Van Damme said 12 days ago:

Does this slow down your app? Are those partials loaded at every request?

635114?v=3&s=64
kobaltz said 12 days ago:

Honestly, I've not done any benchmarking.

However, I would assume that if it is loading the route partials, it would add some kind of overhead. That being said, I would also argue that the overhead would be minimal.  Given the number of controllers that gitlab uses and its performance that i've experienced, any kind of overhead would be worth the added maintainability.

Also, I think that this would just be a valid point on a development environment. By default, if you were to change routes in production, you would have to restart the application to pick up the changes.

Photo
Sam Van Damme said 12 days ago:

Got it, thanks for your comment! Implementing it now :)

00000000000000000000000000000000?d=mm&f=y&s=64
dan.legrand said 9 days ago:

I have a massive routes file on an app I'm working on right now, and it has several namespaced sections, so this episode was just at the right time!

One thought, though.

Instead of: draw :filename, "sub_path", why not use: draw "sub_path/filename" ?

Putting the sub_path as a param after the name seems slightly confusing to me.  When I want to know "where" the file is, it's easier to see "sub_path/filename".  Just a thought!  I found this episode very helpful.

635114?v=3&s=64
kobaltz said 9 days ago:

In hindsight, you're right. Either way should work with the latter being more clear.

142157?v=3&s=64
dancinglightning said 9 days ago:

Nice idea of splitting large route files. 

But am i misunderstanding something or could you just as well use require_relative , either directly or instead of the instance_eval ?

635114?v=3&s=64
kobaltz said 9 days ago:

The code will start to get messy. You wouldn't be able to simply extract out the routes into another file. You would have to still use the Rails.application.routes.draw, but then would also need to figure out how to not overwrite what you've already required. The method that I've demonstrated in this episode was fairly unobtrusive.


You could however, do something like this

config.paths["config/routes"] += Dir[Rails.root.join('config/routes/*.rb’)]

And it would probably have a similar effect, however, I don't think that it is as 'clean'.

Photo
Arkadiusz Oleksy said 9 days ago:

Hi.

You can simplify your draw method to not branch out on subpath. Pathname#join accepts multiple arguments and if one of them is empty string, then it will not be included in created path:

def draw(routes_name, sub_path=nil)
  Rails.root.join('config/routes', sub_path.to_s, "#{routes_name}.rb")
end
draw :v1, 'api'
# => #<Pathname:/path/to/app/config/routes/api/v1.rb>
draw :v1
# => #<Pathname:/path/to/app/config/routes/v1.rb>

Login to Comment