mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
In Browser Path Matching with Javascript
When debugging routes ,it can sometimes be difficult to understand exactly how the paths are matched. This PR adds a JS based path matching widget to the `/rails/info/routes` output. You can enter in a path, and it will tell you which of the routes that path matches, while preserving order (top match wins). The matching widget in action: ![](http://f.cl.ly/items/3A2F0v2m3m1Z1p3P3O3k/path-match.gif) Prior to this PR the only way to check matching paths is via mental math, or typing in a path in the url bar and seeing where it goes. This feature will be an invaluable debugging tool by dramatically decreasing the time needed to check a path match. ATP actionpack
This commit is contained in:
parent
2e8b3d5c9a
commit
8b72d689e3
5 changed files with 135 additions and 13 deletions
|
@ -1,5 +1,10 @@
|
|||
## Rails 4.0.0 (unreleased) ##
|
||||
|
||||
* Add javascript based routing path matcher to `/rails/info/routes`.
|
||||
Routes can now be filtered by whether or not they match a path.
|
||||
|
||||
*Richard Schneeman*
|
||||
|
||||
* Given
|
||||
|
||||
params.permit(:name)
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<td data-route-verb='<%= route[:verb] %>'>
|
||||
<%= route[:verb] %>
|
||||
</td>
|
||||
<td data-route-path='<%= route[:path] %>'>
|
||||
<td data-route-path='<%= route[:path] %>' data-regexp='<%= route[:regexp] %>'>
|
||||
<%= route[:path] %>
|
||||
</td>
|
||||
<td data-route-reqs='<%= route[:reqs] %>'>
|
||||
|
|
|
@ -1,22 +1,58 @@
|
|||
<% content_for :style do %>
|
||||
#route_table td { padding: 0 30px; }
|
||||
#route_table { margin: 0 auto 0; }
|
||||
#route_table {
|
||||
margin: 0 auto 0;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
#route_table td {
|
||||
padding: 0 30px;
|
||||
}
|
||||
|
||||
#route_table tr.bottom th {
|
||||
padding-bottom: 10px;
|
||||
line-height: 15px;
|
||||
}
|
||||
|
||||
#route_table .matched_paths {
|
||||
background-color: LightGoldenRodYellow;
|
||||
}
|
||||
|
||||
#route_table .matched_paths {
|
||||
border-bottom: solid 3px SlateGrey;
|
||||
}
|
||||
|
||||
#path_search {
|
||||
width: 80%;
|
||||
font-size: inherit;
|
||||
}
|
||||
<% end %>
|
||||
|
||||
<table id='route_table' class='route_table'>
|
||||
<thead>
|
||||
<tr>
|
||||
<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>Helper</th>
|
||||
<th>HTTP Verb</th>
|
||||
<th>Path</th>
|
||||
<th>Controller#Action</th>
|
||||
</tr>
|
||||
<tr class='bottom'>
|
||||
<th><%# Helper %>
|
||||
<%= 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 %>
|
||||
<%= search_field(:path, nil, id: 'path_search', placeholder: "Path Match") %>
|
||||
</th>
|
||||
<th><%# Controller#action %>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class='matched_paths' id='matched_paths'>
|
||||
</tbody>
|
||||
<tbody>
|
||||
<%= yield %>
|
||||
</tbody>
|
||||
|
@ -25,7 +61,7 @@
|
|||
<script type='text/javascript'>
|
||||
function each(elems, func) {
|
||||
if (!elems instanceof Array) { elems = [elems]; }
|
||||
for (var i = elems.length; i--; ) {
|
||||
for (var i = 0, len = elems.length; i < len; i++) {
|
||||
func(elems[i]);
|
||||
}
|
||||
}
|
||||
|
@ -46,11 +82,63 @@
|
|||
function setupRouteToggleHelperLinks() {
|
||||
var toggleLinks = document.querySelectorAll('#route_table [data-route-helper]');
|
||||
onClick(toggleLinks, function(){
|
||||
var helperTxt = this.getAttribute("data-route-helper");
|
||||
var helperElems = document.querySelectorAll('[data-route-name] span.helper');
|
||||
var helperTxt = this.getAttribute("data-route-helper"),
|
||||
helperElems = document.querySelectorAll('[data-route-name] span.helper');
|
||||
setValOn(helperElems, helperTxt);
|
||||
});
|
||||
}
|
||||
|
||||
// takes an array of elements with a data-regexp attribute and
|
||||
// passes their their parent <tr> into the callback function
|
||||
// if the regexp matchs a given path
|
||||
function eachElemsForPath(elems, path, func) {
|
||||
each(elems, function(e){
|
||||
var reg = e.getAttribute("data-regexp");
|
||||
if (path.match(RegExp(reg))) {
|
||||
func(e.parentNode.cloneNode(true));
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Ensure path always starts with a slash "/" and remove params or fragments
|
||||
function sanitizePath(path) {
|
||||
var path = path.charAt(0) == '/' ? path : "/" + path;
|
||||
return path.replace(/\#.*|\?.*/, '');
|
||||
}
|
||||
|
||||
// Enables path search functionality
|
||||
function setupMatchPaths() {
|
||||
var regexpElems = document.querySelectorAll('#route_table [data-regexp]'),
|
||||
pathElem = document.querySelector('#path_search'),
|
||||
selectedSection = document.querySelector('#matched_paths'),
|
||||
noMatchText = '<tr><th colspan="4">None</th></tr>';
|
||||
|
||||
|
||||
// Remove matches if no path is present
|
||||
pathElem.onblur = function(e) {
|
||||
if (pathElem.value === "") selectedSection.innerHTML = "";
|
||||
}
|
||||
|
||||
// On key press perform a search for matching paths
|
||||
pathElem.onkeyup = function(e){
|
||||
var path = sanitizePath(pathElem.value),
|
||||
defaultText = '<tr><th colspan="4">Paths Matching (' + path + '):</th></tr>';
|
||||
|
||||
// Clear out results section
|
||||
selectedSection.innerHTML= defaultText;
|
||||
|
||||
// Display matches if they exist
|
||||
eachElemsForPath(regexpElems, path, function(e){
|
||||
selectedSection.appendChild(e);
|
||||
});
|
||||
|
||||
// If no match present, tell the user
|
||||
if (selectedSection.innerHTML === defaultText) {
|
||||
selectedSection.innerHTML = selectedSection.innerHTML + noMatchText;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setupMatchPaths();
|
||||
setupRouteToggleHelperLinks();
|
||||
</script>
|
||||
|
|
|
@ -34,6 +34,23 @@ module ActionDispatch
|
|||
super.to_s
|
||||
end
|
||||
|
||||
def regexp
|
||||
__getobj__.path.to_regexp
|
||||
end
|
||||
|
||||
def json_regexp
|
||||
str = regexp.inspect.
|
||||
sub('\\A' , '^').
|
||||
sub('\\Z' , '$').
|
||||
sub('\\z' , '$').
|
||||
sub(/^\// , '').
|
||||
sub(/\/[a-z]*$/ , '').
|
||||
gsub(/\(\?#.+\)/ , '').
|
||||
gsub(/\(\?-\w+:/ , '(').
|
||||
gsub(/\s/ , '')
|
||||
Regexp.new(str).source
|
||||
end
|
||||
|
||||
def reqs
|
||||
@reqs ||= begin
|
||||
reqs = endpoint
|
||||
|
@ -101,7 +118,11 @@ module ActionDispatch
|
|||
end.collect do |route|
|
||||
collect_engine_routes(route)
|
||||
|
||||
{ name: route.name, verb: route.verb, path: route.path, reqs: route.reqs }
|
||||
{ name: route.name,
|
||||
verb: route.verb,
|
||||
path: route.path,
|
||||
reqs: route.reqs,
|
||||
regexp: route.json_regexp }
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -21,6 +21,14 @@ module ActionDispatch
|
|||
inspector.format(ActionDispatch::Routing::ConsoleFormatter.new, options[:filter]).split("\n")
|
||||
end
|
||||
|
||||
def test_json_regexp_converter
|
||||
@set.draw do
|
||||
get '/cart', :to => 'cart#show'
|
||||
end
|
||||
route = ActionDispatch::Routing::RouteWrapper.new(@set.routes.first)
|
||||
assert_equal "^\\/cart(?:\\.([^\\/.?]+))?$", route.json_regexp
|
||||
end
|
||||
|
||||
def test_displaying_routes_for_engines
|
||||
engine = Class.new(Rails::Engine) do
|
||||
def self.inspect
|
||||
|
|
Loading…
Reference in a new issue