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.split('/').second}.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 => Rails.application.routes.routes.map{|r| r.path.split('/').second}.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).
Nice tip!
Fantastic tip, I’ll be needing to do exactly this same thing very soon on one of my own projects.
I really should read up on all the stuff in the Rails class, lots of powerful stuff there.
You should probably use uniqueness for this use case as far I can tell and be very careful when adding new routes in the future and consider the pros and cons to using a prefix like http://reddit.com/r/rails
Yeah, adding routes in the future was my biggest concern because some record could already hold that route’s name, but I guess that’s solvable by just changing the slug used to define the record’s URL, it would suck to arbitrarily change something user-related (even if the chances are so low), but the trade-off of having easy urls is worth it.
Good call on the uniqueness thing, hehe, I thought it was already in there but I guess I missed it :P