mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
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:
commit
f069b41321
7 changed files with 127 additions and 103 deletions
|
@ -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.
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue