Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Output routes in :html format #8499

Merged
merged 1 commit into from

10 participants

@schneems
Collaborator

By formatting routes for different media (txt/html) we can apply optimizations based on the format. We can include meta-data in the HTML to allow a rich experience while rendering and viewing the routes. This PR shows route helpers as they are used with the _path extension, it also has a javascript toggle on the top to switch to _url. This way the developer can see the exact named route helper they can use instead of having to modify a base.

This is one example of an optimization that could be applied. Eventually we can link out to guides for the different columns to better explain what helper, HTTP Verb, Path, and Controller#action indicate. We could even add a route search box that could allow developers to input a given route and see all of the routes that match it. These are stand alone features and should be delivered separately.


@schneems schneems Output routes in :html format
By formatting routes for different media (txt/html) we can apply optimizations based on the format. We can include meta-data in the HTML to allow a rich experience while rendering and viewing the routes. This PR shows route helpers as they are used with the `_path` extension, it also has a javascript toggle on the top to switch to `_url`. This way the developer can see the exact named route helper they can use instead of having to modify a base. 

This is one example of an optimization that could be applied. Eventually we can link out to guides for the different columns to better explain what helper, HTTP Verb, Path, and Controller#action indicate. We could even add a route search box that could allow developers to input a given route and see all of the routes that match it. These are stand alone features and should be delivered separately.
08d7b18
@steveklabnik
Collaborator

:+1:

@sheerun

sextant on steroids :+1:

@rafaelfranca

I like it.

@spastorino @jeremy thoughts?

@jeremy
Owner

Dig the idea and end result. The hard-coded format conditionals seem funky, though.

@carlosantoniodasilva

It could have separate formatters that are passed to the format method. So instead of calling

format(..., :html)

You could then inject the formatter:

format(... HTMLFormatter.new)

Or use the other way around:

HTMLFormatter.new(inspector.collect_all_routes(_routes.routes)).format.
@rafaelfranca

Yes, I'd propose this refactoring after we accept the change.

@steveklabnik
Collaborator

Well, my vote is :shipit: then.

@gaurish

good idea :+1:

@carlosantoniodasilva

@rafaelfranca right, seems :+1: to me so.

@steveklabnik steveklabnik merged commit ae68fc3 into from
@steveklabnik
Collaborator

Okay then!

@steveklabnik
Collaborator

Oh! @schneems I forgot to bug you about a CHANGELOG. Let's do that with the refactoring, eh?

@coreyhaines

Just a thought on an improvement. One confusion is the implicit "look at the route helper name above" if there isn't anything in the column. Could we have this repeat each route helper name in the left column?

Pretty neat to see this, though.

@jacortinas

It would be incredible if the first occurrence of the route helper name was bold or black and every duplicate occurrence was a lighter shade of gray. /cc @coreyhaines

@dhh
Owner

How and where is this output used? It feels like half a PR.

@steveklabnik
Collaborator
@dhh
Owner
@steveklabnik
Collaborator

I'm not so big a fan of having all this HTML generation shoved into the routing code itself, though.

@dhh I agree. This is what @carlosantoniodasilva suggested here: #8499 (comment)

@schneems is gonna grab this, and if he doesn't in a few days, then I am, since it's my fault for merging early.

IMO this should be a separate engine that follows a regular mvc setup.

Check out #6696. Routes are now shown in the browser upon routing errors. It's not quite an Engine, exactly...

@dhh
Owner

That's a nice use case for it. But if we're going to expose it elsewhere, I say we get started on that Conductor approach.

In the mean time it seems like we should revert this as this is never going to be the implementation of it?

@schneems
Collaborator

@dhh the original functionality was put in a Gem https://rubygems.org/gems/sextant that has had over thirty-three thousand downloads in the last few months, it was added into Rails 4 after some on the core team reached out to me. I wrote a blog post on the ideals behind this and my other work in Raise Hell: Better programming through Error Messages.

I teach Rails at the University of Texas and this functionality came straight from user research with my students, as well as requests from seasoned Rails devs. The Engine is very limited in capacity, i'll get to that but first a quick recap on reasoning behind adding this functionality:

  1. Moving the routing into the Rails app, eliminates the need to boot the app to spit out the routes (such as when running rake routes). For some larger apps i've worked on this cuts down the time to see the routes from 30 seconds + to nearly instantaneously. This insanely reduced time allows for quick iteration, and less hairpulling. The Engine can give us this, but that is about it.
  2. When you get a routing error, it's because you either mis-typed something or you misinterpreted something in your routes. I can't help with the first, but we can show you the result of your routes on the error page. This info is presented just-in-time and gives you everything you need to fix your mistake. An engine doesn't have the hooks to add information to one of the built in error pages without totally over-writing it.
  3. The intent of this yet to be completed PR as @steveklabnik mentioned, is to add semantics and additional info to the rendering of the information which is not possible while outputting to the console.
  4. While these three pieces are great for seasoned devs, where they really shines is for beginners. When someone is first learning Rails, Routing is one of the hardest things to grok, making this information harder to find, and longer to get to only adds to the pain and confusion. Having it baked into Rails means it is available out of the box, with no configuration needed means developers of all skill levels have this information at their fingertips.

If you want to revert this PR, I'm all for it... it is still a WIP, I ask that you don't revert the other functionality without the pieces in place to provide an adequate replacement. I would be interested in helping out more with adding in some of those hooks if you're willing to give me some guidance.

@steveklabnik steveklabnik referenced this pull request from a commit in steveklabnik/rails
@steveklabnik steveklabnik Revert "Merge pull request #8499 from schneems/schneems/html-route-in…
…spector"

This reverts commit ae68fc3, reversing
changes made to 0262a18.

See here: rails#8499 (comment)
8554537
@steveklabnik
Collaborator

Reverted in 8554537 as requested.

@schneems
Collaborator

Thanks @steveklabnik

@dhh
Owner

@schneems It's a great feature. What we are discussing here is the implementation. Having the routing code know how to format HTML doesn't feel like its the right level of abstraction. Just like your model classes don't format HTML either.

I could see returning a json representation that can be transformed into HTML where that makes sense.

@schneems
Collaborator

@dhh I agree completely that the implementation is flawed, returning a collection and passing that to a partial or moving to some kind of presenter would even be a big step forwards. I'll iterate on the implementation a bit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Dec 12, 2012
  1. @schneems

    Output routes in :html format

    schneems authored
    By formatting routes for different media (txt/html) we can apply optimizations based on the format. We can include meta-data in the HTML to allow a rich experience while rendering and viewing the routes. This PR shows route helpers as they are used with the `_path` extension, it also has a javascript toggle on the top to switch to `_url`. This way the developer can see the exact named route helper they can use instead of having to modify a base. 
    
    This is one example of an optimization that could be applied. Eventually we can link out to guides for the different columns to better explain what helper, HTTP Verb, Path, and Controller#action indicate. We could even add a route search box that could allow developers to input a given route and see all of the routes that match it. These are stand alone features and should be delivered separately.
This page is out of date. Refresh to see the latest.
View
31 actionpack/lib/action_dispatch/routing/inspector.rb
@@ -67,15 +67,19 @@ def initialize
@engines = Hash.new
end
- def format(all_routes, filter = nil)
+ def format(all_routes, filter = nil, format = :txt)
if filter
all_routes = all_routes.select{ |route| route.defaults[:controller] == filter }
end
routes = collect_routes(all_routes)
- formatted_routes(routes) +
- formatted_routes_for_engines
+ routes = formatted_routes(routes, format) + formatted_routes_for_engines(format)
+ if format == :html
+ routes.join('')
+ else
+ routes
+ end
end
def collect_routes(routes)
@@ -101,19 +105,32 @@ def collect_engine_routes(route)
end
end
- def formatted_routes_for_engines
+ def formatted_routes_for_engines(format)
@engines.map do |name, routes|
- ["\nRoutes for #{name}:"] + formatted_routes(routes)
+ ["\nRoutes for #{name}:"] + formatted_routes(routes, format)
end.flatten
end
- def formatted_routes(routes)
+ def formatted_routes(routes, format)
name_width = routes.map{ |r| r[:name].length }.max
verb_width = routes.map{ |r| r[:verb].length }.max
path_width = routes.map{ |r| r[:path].length }.max
routes.map do |r|
- "#{r[:name].rjust(name_width)} #{r[:verb].ljust(verb_width)} #{r[:path].ljust(path_width)} #{r[:reqs]}"
+ if format == :txt
+ "#{r[:name].rjust(name_width)} " +
+ "#{r[:verb].ljust(verb_width)} " +
+ "#{r[:path].ljust(path_width)} " +
+ "#{r[:reqs]}"
+ elsif format == :html
+ route = r
+ "<tr class='route-row' data-helper='path' #{[:name, :verb, :path, :reqs].each {|key| "data-#{key}='#{route[key]}'"} } >" +
+ "<td class='route-name'>#{route[:name] + "<span class='helper'>_path</span>" if route[:name].present?}</td>" +
+ "<td class='route-verb'>#{route[:verb]}</td>" +
+ "<td class='route-path'>#{route[:path]}</td>" +
+ "<td class='route-reqs'>#{route[:reqs]}</td>" +
+ "</tr>"
+ end
end
end
end
View
2  railties/lib/rails/info_controller.rb
@@ -16,7 +16,7 @@ def properties
def routes
inspector = ActionDispatch::Routing::RoutesInspector.new
- @info = inspector.format(_routes.routes).join("\n")
+ @info = inspector.format(_routes.routes, nil, :html)
end
protected
View
27 railties/lib/rails/templates/rails/info/routes.html.erb
@@ -1,3 +1,7 @@
+<style>
+.route-row td {padding: 0 30px;}
+.routeTable {margin: 0 auto 0;}
+</style>
<h2>
Routes
</h2>
@@ -6,4 +10,25 @@
Routes match in priority from top to bottom
</p>
-<p><pre><%= @info %></pre></p>
+<table id='routeTable' class='routeTable'>
+ <th>Helper<br />
+ <%= link_to "Path", "#", 'data-route-helper' => 'path',
+ title: "Returns a relative path (without the http or domain)" %> /
+ <%= link_to "Url", "#", 'data-route-helper' => 'url',
+ title: "Returns an absolute url (with the http and domain)" %>
+ </th>
+ <th>HTTP Verb</th>
+ <th>Path</th>
+ <th>Controller#Action</th>
+ <%= @info.html_safe %>
+</table>
+
+<script type='text/javascript'>
+ $(document).ready(function (){
+ $("#routeTable [data-route-helper]").on('click', function(){
+ routeHelper = $(this).data("route-helper");
+ $('.route-name span.helper').html("_" + routeHelper);
+ return false;
+ })
+ })
+</script>
Something went wrong with that request. Please try again.