1
0
Fork 0
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:
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* *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.

View file

@ -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>

View file

@ -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');

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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