Recently on my side project I came across an interesting problem: how could I get all the route paths in my Rails 3 app at runtime? I needed a way to get this information because the app uses a record’s attribute to directly set the path to it through a friendly URL, such as http://universit.as/ruby, so if a user created a record called ‘dashboard’, that would conflict with the ‘dashboard’ path and always show the dashboard page instead of the intended record page. As you can see, this is a terrible security flaw if we consider more serious scenarios.

Back to the initial problem, I figured I could work with a result similar to what the well-known “rake routes” prints out, so there I went to the Rails source code to see how it worked: https://github.com/rails/rails/blob/master/railties/lib/rails/tasks/routes.rake

After looking at it for 5 seconds or so, we can immediately notice how easy it is to get the app’s routes:

all_routes = Rails.application.routes.routes

Although it seems like that loading the whole app’s route mapping would be slow, it actually isn’t because they’re only loaded once when the Rails environment is loaded (when the server starts), so you basically get this for free.

So after mapping that array to the route’s path (you can get other attributes too, check the Rails source link) and doing some string manipulation, I managed to get a pretty satisfying result:

Rails.application.routes.routes.map{|r| r.path.spec.to_s.split('/').third}.compact.uniq
# currently returns ["profile", "users", "users(.:format)", "groups", "groups(.:format)", "documents", "documents(.:format)", "updates(.:format)", "updates", "authentications(.:format)", "authentications", "home(.:format)", "about(.:format)", "track.js(.:format)", "textile_guide(.:format)", ":id(.:format)", "auth", "rails"] 

It’s not perfect but it’s definitely something I can work with on a validation like so:

validates :name, :uniqueness => true, :presence => true, :exclusion => {:in => lambda { |u| return Rails.application.routes.routes.map{|r| r.path.spec.to_s.split('/').third.try(:gsub, /\(.*\)/, '')}.compact.uniq}}

Hope this small tip saves the next dev some time (looking at Rails source wasn’t my first action either, although it should be).

About these ads