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*
|
*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
|
* Non-string authenticity tokens do not raise NoMethodError when decoding
|
||||||
the masked token.
|
the masked token.
|
||||||
|
|
||||||
|
|
|
@ -4,13 +4,13 @@
|
||||||
<%= route[:name] %><span class='helper'>_path</span>
|
<%= route[:name] %><span class='helper'>_path</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
</td>
|
</td>
|
||||||
<td data-route-verb='<%= route[:verb] %>'>
|
<td>
|
||||||
<%= route[:verb] %>
|
<%= route[:verb] %>
|
||||||
</td>
|
</td>
|
||||||
<td data-route-path='<%= route[:path] %>' data-regexp='<%= route[:regexp] %>'>
|
<td data-route-path='<%= route[:path] %>'>
|
||||||
<%= route[:path] %>
|
<%= route[:path] %>
|
||||||
</td>
|
</td>
|
||||||
<td data-route-reqs='<%= route[:reqs] %>'>
|
<td>
|
||||||
<%= route[:reqs] %>
|
<%=simple_format route[:reqs] %>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
@ -81,92 +81,87 @@
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<script type='text/javascript'>
|
<script type='text/javascript'>
|
||||||
// Iterates each element through a function
|
// support forEarch iterator on NodeList
|
||||||
function each(elems, func) {
|
NodeList.prototype.forEach = Array.prototype.forEach;
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Enables path search functionality
|
// Enables path search functionality
|
||||||
function setupMatchPaths() {
|
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
|
// Check if there are any matched results in a section
|
||||||
function checkNoMatch(section, defaultText, noMatchText) {
|
function checkNoMatch(section, noMatchText) {
|
||||||
if (section.innerHTML === defaultText) {
|
if (section.children.length <= 1) {
|
||||||
setContent(section, defaultText + noMatchText);
|
section.innerHTML += noMatchText;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure path always starts with a slash "/" and remove params or fragments
|
// 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
function delayedKeyup(input, callback) {
|
||||||
|
var timeout;
|
||||||
|
input.onkeyup = function(){
|
||||||
|
if (timeout) clearTimeout(timeout);
|
||||||
|
timeout = setTimeout(callback, 300);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove params or fragments
|
||||||
function sanitizePath(path) {
|
function sanitizePath(path) {
|
||||||
var path = path.charAt(0) == '/' ? path : "/" + path;
|
return path.replace(/[#?].*/, '');
|
||||||
return path.replace(/\#.*|\?.*/, '');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var regexpElems = document.querySelectorAll('#route_table [data-regexp]'),
|
var pathElements = document.querySelectorAll('#route_table [data-route-path]'),
|
||||||
searchElem = document.querySelector('#search'),
|
searchElem = document.querySelector('#search'),
|
||||||
exactMatches = document.querySelector('#exact_matches'),
|
exactSection = document.querySelector('#exact_matches'),
|
||||||
fuzzyMatches = document.querySelector('#fuzzy_matches');
|
fuzzySection = document.querySelector('#fuzzy_matches');
|
||||||
|
|
||||||
// Remove matches when no search value is present
|
// Remove matches when no search value is present
|
||||||
searchElem.onblur = function(e) {
|
searchElem.onblur = function(e) {
|
||||||
if (searchElem.value === "") {
|
if (searchElem.value === "") {
|
||||||
setContent(exactMatches, "");
|
exactSection.innerHTML = "";
|
||||||
setContent(fuzzyMatches, "");
|
fuzzySection.innerHTML = "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// On key press perform a search for matching paths
|
// On key press perform a search for matching paths
|
||||||
searchElem.onkeyup = function(e){
|
delayedKeyup(searchElem, function() {
|
||||||
var userInput = searchElem.value,
|
var path = sanitizePath(searchElem.value),
|
||||||
defaultExactMatch = '<tr><th colspan="4">Paths Matching (' + escape(sanitizePath(userInput)) +'):</th></tr>',
|
defaultExactMatch = '<tr><th colspan="4">Paths Matching (' + path +'):</th></tr>',
|
||||||
defaultFuzzyMatch = '<tr><th colspan="4">Paths Containing (' + escape(userInput) +'):</th></tr>',
|
defaultFuzzyMatch = '<tr><th colspan="4">Paths Containing (' + path +'):</th></tr>',
|
||||||
noExactMatch = '<tr><th colspan="4">No Exact Matches Found</th></tr>',
|
noExactMatch = '<tr><th colspan="4">No Exact Matches Found</th></tr>',
|
||||||
noFuzzyMatch = '<tr><th colspan="4">No Fuzzy Matches Found</th></tr>';
|
noFuzzyMatch = '<tr><th colspan="4">No Fuzzy Matches Found</th></tr>';
|
||||||
|
|
||||||
|
if (!path)
|
||||||
|
return searchElem.onblur();
|
||||||
|
|
||||||
|
getJSON('/rails/info/routes?path=' + path, function(matches){
|
||||||
// Clear out results section
|
// Clear out results section
|
||||||
setContent(exactMatches, defaultExactMatch);
|
exactSection.innerHTML = defaultExactMatch;
|
||||||
setContent(fuzzyMatches, defaultFuzzyMatch);
|
fuzzySection.innerHTML = defaultFuzzyMatch;
|
||||||
|
|
||||||
// Display exact matches and fuzzy matches
|
// Display exact matches and fuzzy matches
|
||||||
each(regexpElems, function(elem) {
|
pathElements.forEach(function(elem) {
|
||||||
checkExactMatch(exactMatches, elem, userInput);
|
var elemPath = elem.getAttribute('data-route-path');
|
||||||
checkFuzzyMatch(fuzzyMatches, elem, userInput);
|
|
||||||
|
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
|
// Display 'No Matches' message when no matches are found
|
||||||
checkNoMatch(exactMatches, defaultExactMatch, noExactMatch);
|
checkNoMatch(exactSection, noExactMatch);
|
||||||
checkNoMatch(fuzzyMatches, defaultFuzzyMatch, noFuzzyMatch);
|
checkNoMatch(fuzzySection, noFuzzyMatch);
|
||||||
}
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enables functionality to toggle between `_path` and `_url` helper suffixes
|
// Enables functionality to toggle between `_path` and `_url` helper suffixes
|
||||||
|
@ -174,19 +169,20 @@
|
||||||
|
|
||||||
// Sets content for each element
|
// Sets content for each element
|
||||||
function setValOn(elems, val) {
|
function setValOn(elems, val) {
|
||||||
each(elems, function(elem) {
|
elems.forEach(function(elem) {
|
||||||
setContent(elem, val);
|
elem.innerHTML = val;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sets onClick event for each element
|
// Sets onClick event for each element
|
||||||
function onClick(elems, func) {
|
function onClick(elems, func) {
|
||||||
each(elems, function(elem) {
|
elems.forEach(function(elem) {
|
||||||
elem.onclick = func;
|
elem.onclick = func;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
var toggleLinks = document.querySelectorAll('#route_table [data-route-helper]');
|
var toggleLinks = document.querySelectorAll('#route_table [data-route-helper]');
|
||||||
|
|
||||||
onClick(toggleLinks, function(){
|
onClick(toggleLinks, function(){
|
||||||
var helperTxt = this.getAttribute("data-route-helper"),
|
var helperTxt = this.getAttribute("data-route-helper"),
|
||||||
helperElems = document.querySelectorAll('[data-route-name] span.helper');
|
helperElems = document.querySelectorAll('[data-route-name] span.helper');
|
||||||
|
|
|
@ -28,23 +28,6 @@ module ActionDispatch
|
||||||
super.to_s
|
super.to_s
|
||||||
end
|
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
|
def reqs
|
||||||
@reqs ||= begin
|
@reqs ||= begin
|
||||||
reqs = endpoint
|
reqs = endpoint
|
||||||
|
@ -120,8 +103,7 @@ module ActionDispatch
|
||||||
{ name: route.name,
|
{ name: route.name,
|
||||||
verb: route.verb,
|
verb: route.verb,
|
||||||
path: route.path,
|
path: route.path,
|
||||||
reqs: route.reqs,
|
reqs: route.reqs }
|
||||||
regexp: route.json_regexp }
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -26,14 +26,6 @@ module ActionDispatch
|
||||||
inspector.format(ActionDispatch::Routing::ConsoleFormatter.new, options[:filter]).split("\n")
|
inspector.format(ActionDispatch::Routing::ConsoleFormatter.new, options[:filter]).split("\n")
|
||||||
end
|
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
|
def test_displaying_routes_for_engines
|
||||||
engine = Class.new(Rails::Engine) do
|
engine = Class.new(Rails::Engine) do
|
||||||
def self.inspect
|
def self.inspect
|
||||||
|
|
|
@ -17,7 +17,28 @@ class Rails::InfoController < Rails::ApplicationController # :nodoc:
|
||||||
end
|
end
|
||||||
|
|
||||||
def routes
|
def 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)
|
@routes_inspector = ActionDispatch::Routing::RoutesInspector.new(_routes.routes)
|
||||||
@page_title = 'Routes'
|
@page_title = 'Routes'
|
||||||
end
|
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
|
end
|
||||||
|
|
|
@ -53,4 +53,29 @@ class InfoControllerTest < ActionController::TestCase
|
||||||
assert_response :success
|
assert_response :success
|
||||||
end
|
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
|
end
|
||||||
|
|
Loading…
Reference in a new issue