Merge pull request #18434 from brainopia/change_filter_on_rails_info_routes

Change filter on /rails/info/routes to use an actual path regexp from rails
This commit is contained in:
Richard Schneeman 2015-02-26 12:59:43 -06:00
commit f069b41321
7 changed files with 127 additions and 103 deletions

View File

@ -16,6 +16,14 @@
*David Ilizarov*
* Change filter on /rails/info/routes to use an actual path regexp from rails
and not approximate javascript version. Oniguruma supports much more
extensive list of features than javascript regexp engine.
Fixes #18402.
*Ravil Bayramgalin*
* Non-string authenticity tokens do not raise NoMethodError when decoding
the masked token.

View File

@ -4,13 +4,13 @@
<%= route[:name] %><span class='helper'>_path</span>
<% end %>
</td>
<td data-route-verb='<%= route[:verb] %>'>
<td>
<%= route[:verb] %>
</td>
<td data-route-path='<%= route[:path] %>' data-regexp='<%= route[:regexp] %>'>
<td data-route-path='<%= route[:path] %>'>
<%= route[:path] %>
</td>
<td data-route-reqs='<%= route[:reqs] %>'>
<%= route[:reqs] %>
<td>
<%=simple_format route[:reqs] %>
</td>
</tr>

View File

@ -81,92 +81,87 @@
</table>
<script type='text/javascript'>
// Iterates each element through a function
function each(elems, func) {
if (!elems instanceof Array) { elems = [elems]; }
for (var i = 0, len = elems.length; i < len; i++) {
func(elems[i]);
}
}
// Sets innerHTML for an element
function setContent(elem, text) {
elem.innerHTML = text;
}
// support forEarch iterator on NodeList
NodeList.prototype.forEach = Array.prototype.forEach;
// Enables path search functionality
function setupMatchPaths() {
// Check if the user input (sanitized as a path) matches the regexp data attribute
function checkExactMatch(section, elem, value) {
var string = sanitizePath(value),
regexp = elem.getAttribute("data-regexp");
showMatch(string, regexp, section, elem);
}
// Check if the route path data attribute contains the user input
function checkFuzzyMatch(section, elem, value) {
var string = elem.getAttribute("data-route-path"),
regexp = value;
showMatch(string, regexp, section, elem);
}
// Display the parent <tr> element in the appropriate section when there's a match
function showMatch(string, regexp, section, elem) {
if(string.match(RegExp(regexp))) {
section.appendChild(elem.parentNode.cloneNode(true));
}
}
// Check if there are any matched results in a section
function checkNoMatch(section, defaultText, noMatchText) {
if (section.innerHTML === defaultText) {
setContent(section, defaultText + noMatchText);
function checkNoMatch(section, noMatchText) {
if (section.children.length <= 1) {
section.innerHTML += noMatchText;
}
}
// 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(/\#.*|\?.*/, '');
// get JSON from url and invoke callback with result
function getJSON(url, success) {
var xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.onload = function() {
if (this.status == 200)
success(JSON.parse(this.response));
};
xhr.send();
}
var regexpElems = document.querySelectorAll('#route_table [data-regexp]'),
searchElem = document.querySelector('#search'),
exactMatches = document.querySelector('#exact_matches'),
fuzzyMatches = document.querySelector('#fuzzy_matches');
function delayedKeyup(input, callback) {
var timeout;
input.onkeyup = function(){
if (timeout) clearTimeout(timeout);
timeout = setTimeout(callback, 300);
}
}
// remove params or fragments
function sanitizePath(path) {
return path.replace(/[#?].*/, '');
}
var pathElements = document.querySelectorAll('#route_table [data-route-path]'),
searchElem = document.querySelector('#search'),
exactSection = document.querySelector('#exact_matches'),
fuzzySection = document.querySelector('#fuzzy_matches');
// Remove matches when no search value is present
searchElem.onblur = function(e) {
if (searchElem.value === "") {
setContent(exactMatches, "");
setContent(fuzzyMatches, "");
exactSection.innerHTML = "";
fuzzySection.innerHTML = "";
}
}
// On key press perform a search for matching paths
searchElem.onkeyup = function(e){
var userInput = searchElem.value,
defaultExactMatch = '<tr><th colspan="4">Paths Matching (' + escape(sanitizePath(userInput)) +'):</th></tr>',
defaultFuzzyMatch = '<tr><th colspan="4">Paths Containing (' + escape(userInput) +'):</th></tr>',
delayedKeyup(searchElem, function() {
var path = sanitizePath(searchElem.value),
defaultExactMatch = '<tr><th colspan="4">Paths Matching (' + path +'):</th></tr>',
defaultFuzzyMatch = '<tr><th colspan="4">Paths Containing (' + path +'):</th></tr>',
noExactMatch = '<tr><th colspan="4">No Exact Matches Found</th></tr>',
noFuzzyMatch = '<tr><th colspan="4">No Fuzzy Matches Found</th></tr>';
// Clear out results section
setContent(exactMatches, defaultExactMatch);
setContent(fuzzyMatches, defaultFuzzyMatch);
if (!path)
return searchElem.onblur();
// Display exact matches and fuzzy matches
each(regexpElems, function(elem) {
checkExactMatch(exactMatches, elem, userInput);
checkFuzzyMatch(fuzzyMatches, elem, userInput);
getJSON('/rails/info/routes?path=' + path, function(matches){
// Clear out results section
exactSection.innerHTML = defaultExactMatch;
fuzzySection.innerHTML = defaultFuzzyMatch;
// Display exact matches and fuzzy matches
pathElements.forEach(function(elem) {
var elemPath = elem.getAttribute('data-route-path');
if (matches['exact'].indexOf(elemPath) != -1)
exactSection.appendChild(elem.parentNode.cloneNode(true));
if (matches['fuzzy'].indexOf(elemPath) != -1)
fuzzySection.appendChild(elem.parentNode.cloneNode(true));
})
// Display 'No Matches' message when no matches are found
checkNoMatch(exactSection, noExactMatch);
checkNoMatch(fuzzySection, noFuzzyMatch);
})
// Display 'No Matches' message when no matches are found
checkNoMatch(exactMatches, defaultExactMatch, noExactMatch);
checkNoMatch(fuzzyMatches, defaultFuzzyMatch, noFuzzyMatch);
}
})
}
// Enables functionality to toggle between `_path` and `_url` helper suffixes
@ -174,19 +169,20 @@
// Sets content for each element
function setValOn(elems, val) {
each(elems, function(elem) {
setContent(elem, val);
elems.forEach(function(elem) {
elem.innerHTML = val;
});
}
// Sets onClick event for each element
function onClick(elems, func) {
each(elems, function(elem) {
elems.forEach(function(elem) {
elem.onclick = func;
});
}
var toggleLinks = document.querySelectorAll('#route_table [data-route-helper]');
onClick(toggleLinks, function(){
var helperTxt = this.getAttribute("data-route-helper"),
helperElems = document.querySelectorAll('[data-route-name] span.helper');

View File

@ -28,23 +28,6 @@ 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
@ -117,11 +100,10 @@ module ActionDispatch
end.reject(&:internal?).collect do |route|
collect_engine_routes(route)
{ name: route.name,
verb: route.verb,
path: route.path,
reqs: route.reqs,
regexp: route.json_regexp }
{ name: route.name,
verb: route.verb,
path: route.path,
reqs: route.reqs }
end
end

View File

@ -26,14 +26,6 @@ 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

View File

@ -17,7 +17,28 @@ class Rails::InfoController < Rails::ApplicationController # :nodoc:
end
def routes
@routes_inspector = ActionDispatch::Routing::RoutesInspector.new(_routes.routes)
@page_title = 'Routes'
if path = params[:path]
path = URI.escape path
normalized_path = with_leading_slash path
render json: {
exact: match_route {|it| it.match normalized_path },
fuzzy: match_route {|it| it.spec.to_s.match path }
}
else
@routes_inspector = ActionDispatch::Routing::RoutesInspector.new(_routes.routes)
@page_title = 'Routes'
end
end
private
def match_route
_routes.routes.select {|route|
yield route.path
}.map {|route| route.path.spec.to_s }
end
def with_leading_slash(path)
('/' + path).squeeze('/')
end
end

View File

@ -53,4 +53,29 @@ class InfoControllerTest < ActionController::TestCase
assert_response :success
end
test "info controller returns exact matches" do
exact_count = -> { JSON(response.body)['exact'].size }
get :routes, path: 'rails/info/route'
assert exact_count.call == 0, 'should not match incomplete routes'
get :routes, path: 'rails/info/routes'
assert exact_count.call == 1, 'should match complete routes'
get :routes, path: 'rails/info/routes.html'
assert exact_count.call == 1, 'should match complete routes with optional parts'
end
test "info controller returns fuzzy matches" do
fuzzy_count = -> { JSON(response.body)['fuzzy'].size }
get :routes, path: 'rails/info'
assert fuzzy_count.call == 2, 'should match incomplete routes'
get :routes, path: 'rails/info/routes'
assert fuzzy_count.call == 1, 'should match complete routes'
get :routes, path: 'rails/info/routes.html'
assert fuzzy_count.call == 0, 'should match optional parts of route literally'
end
end