diff options
Diffstat (limited to 'actionpack')
-rw-r--r-- | actionpack/CHANGELOG.md | 14 | ||||
-rw-r--r-- | actionpack/lib/action_dispatch/middleware/session/cookie_store.rb | 2 | ||||
-rw-r--r-- | actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb | 182 | ||||
-rw-r--r-- | actionpack/lib/action_dispatch/routing.rb | 2 | ||||
-rw-r--r-- | actionpack/lib/action_dispatch/routing/mapper.rb | 15 | ||||
-rw-r--r-- | actionpack/test/dispatch/routing_test.rb | 18 |
6 files changed, 165 insertions, 68 deletions
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index e70e0b5fa7..513f2e66fb 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,9 +1,19 @@ +* Improve routing error page with fuzzy matching search. + + *Winston* + +* Only make deeply nested routes shallow when parent is shallow. + + Fixes #14684. + + *Andrew White*, *James Coglan* + * Append link to bad code to backtrace when exception is SyntaxError. *Boris Kuznetsov* - + * Swapped the parameters of assert_equal in `assert_select` so that the - proper values were printed correctly + proper values were printed correctly Fixes #14422. diff --git a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb index 3be0ce1860..0864e7ef2a 100644 --- a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb +++ b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb @@ -29,7 +29,7 @@ module ActionDispatch # # Configure your session store in config/initializers/session_store.rb: # - # Myapp::Application.config.session_store :cookie_store, key: '_your_app_session' + # Rails.application.config.session_store :cookie_store, key: '_your_app_session' # # Configure your secret key in config/secrets.yml: # diff --git a/actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb b/actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb index 323873ba4b..cce0d75af4 100644 --- a/actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb +++ b/actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb @@ -4,21 +4,41 @@ border-collapse: collapse; } - #route_table td { - padding: 0 30px; + #route_table thead tr { + border-bottom: 2px solid #ddd; + } + + #route_table thead tr.bottom { + border-bottom: none; } - #route_table tr.bottom th { - padding-bottom: 10px; + #route_table thead tr.bottom th { + padding: 10px 0; line-height: 15px; } - #route_table .matched_paths { + #route_table tbody tr { + border-bottom: 1px solid #ddd; + } + + #route_table tbody tr:nth-child(odd) { + background: #f2f2f2; + } + + #route_table tbody.exact_matches, + #route_table tbody.fuzzy_matches { background-color: LightGoldenRodYellow; + border-bottom: solid 2px SlateGrey; } - #route_table .matched_paths { - border-bottom: solid 3px SlateGrey; + #route_table tbody.exact_matches tr, + #route_table tbody.fuzzy_matches tr { + background: none; + border-bottom: none; + } + + #route_table td { + padding: 4px 30px; } #path_search { @@ -45,13 +65,15 @@ <th><%# HTTP Verb %> </th> <th><%# Path %> - <%= search_field(:path, nil, id: 'path_search', placeholder: "Path Match") %> + <%= search_field(:path, nil, id: 'search', placeholder: "Path Match") %> </th> <th><%# Controller#action %> </th> </tr> </thead> - <tbody class='matched_paths' id='matched_paths'> + <tbody class='exact_matches' id='exact_matches'> + </tbody> + <tbody class='fuzzy_matches' id='fuzzy_matches'> </tbody> <tbody> <%= yield %> @@ -59,6 +81,7 @@ </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++) { @@ -66,77 +89,110 @@ } } - function setValOn(elems, val) { - each(elems, function(elem) { - elem.innerHTML = val; - }); + // Sets innerHTML for an element + function setContent(elem, text) { + elem.innerHTML = text; } - function onClick(elems, func) { - each(elems, function(elem) { - elem.onclick = func; - }); - } + // 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"); - // Enables functionality to toggle between `_path` and `_url` helper suffixes - function setupRouteToggleHelperLinks() { - 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'); - setValOn(helperElems, helperTxt); - }); - } + showMatch(string, regexp, section, elem); + } - // takes an array of elements with a data-regexp attribute and - // passes their parent <tr> into the callback function - // if the regexp matches 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)); - } - }) - } + // 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; - // 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(/\#.*|\?.*/, ''); - } + showMatch(string, regexp, section, elem); + } - // 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>'; + // 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); + } + } + // 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(/\#.*|\?.*/, ''); + } - // Remove matches if no path is present - pathElem.onblur = function(e) { - if (pathElem.value === "") selectedSection.innerHTML = ""; + var regexpElems = document.querySelectorAll('#route_table [data-regexp]'), + searchElem = document.querySelector('#search'), + exactMatches = document.querySelector('#exact_matches'), + fuzzyMatches = document.querySelector('#fuzzy_matches'); + + // Remove matches when no search value is present + searchElem.onblur = function(e) { + if (searchElem.value === "") { + setContent(exactMatches, ""); + setContent(fuzzyMatches, ""); + } } // 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>'; + searchElem.onkeyup = function(e){ + var userInput = searchElem.value, + defaultExactMatch = '<tr><th colspan="4">Paths Matching (' + sanitizePath(userInput) +'):</th></tr>', + defaultFuzzyMatch = '<tr><th colspan="4">Paths Containing (' + userInput +'):</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 - selectedSection.innerHTML= defaultText; + setContent(exactMatches, defaultExactMatch); + setContent(fuzzyMatches, defaultFuzzyMatch); + + // Display exact matches and fuzzy matches + each(regexpElems, function(elem) { + checkExactMatch(exactMatches, elem, userInput); + checkFuzzyMatch(fuzzyMatches, elem, userInput); + }) + + // Display 'No Matches' message when no matches are found + checkNoMatch(exactMatches, defaultExactMatch, noExactMatch); + checkNoMatch(fuzzyMatches, defaultFuzzyMatch, noFuzzyMatch); + } + } - // Display matches if they exist - eachElemsForPath(regexpElems, path, function(e){ - selectedSection.appendChild(e); + // Enables functionality to toggle between `_path` and `_url` helper suffixes + function setupRouteToggleHelperLinks() { + + // Sets content for each element + function setValOn(elems, val) { + each(elems, function(elem) { + setContent(elem, val); }); + } - // If no match present, tell the user - if (selectedSection.innerHTML === defaultText) { - selectedSection.innerHTML = selectedSection.innerHTML + noMatchText; - } + // Sets onClick event for each element + function onClick(elems, func) { + each(elems, 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'); + + setValOn(helperElems, helperTxt); + }); } setupMatchPaths(); diff --git a/actionpack/lib/action_dispatch/routing.rb b/actionpack/lib/action_dispatch/routing.rb index a9ac2bce1d..9cd884daa3 100644 --- a/actionpack/lib/action_dispatch/routing.rb +++ b/actionpack/lib/action_dispatch/routing.rb @@ -11,7 +11,7 @@ module ActionDispatch # Think of creating routes as drawing a map for your requests. The map tells # them where to go based on some predefined pattern: # - # AppName::Application.routes.draw do + # Rails.application.routes.draw do # Pattern 1 tells some request to go to one place # Pattern 2 tell them to go to another # ... diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index 6f0b49cf28..77718a14c1 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -995,6 +995,7 @@ module ActionDispatch @as = options[:as] @param = (options[:param] || :id).to_sym @options = options + @shallow = false end def default_actions @@ -1055,6 +1056,13 @@ module ActionDispatch "#{path}/:#{nested_param}" end + def shallow=(value) + @shallow = value + end + + def shallow? + @shallow + end end class SingletonResource < Resource #:nodoc: @@ -1361,7 +1369,7 @@ module ActionDispatch end with_scope_level(:nested) do - if shallow? && nesting_depth > 1 + if shallow? && shallow_nesting_depth > 1 shallow_scope(parent_resource.nested_scope, nested_options) { yield } else scope(parent_resource.nested_scope, nested_options) { yield } @@ -1576,6 +1584,7 @@ module ActionDispatch end def resource_scope(kind, resource) #:nodoc: + resource.shallow = @scope[:shallow] old_resource, @scope[:scope_level_resource] = @scope[:scope_level_resource], resource @nesting.push(resource) @@ -1600,6 +1609,10 @@ module ActionDispatch @nesting.size end + def shallow_nesting_depth #:nodoc: + @nesting.select(&:shallow?).size + end + def param_constraint? #:nodoc: @scope[:constraints] && @scope[:constraints][parent_resource.param].is_a?(Regexp) end diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb index ab2f0ec8de..f74a0ef945 100644 --- a/actionpack/test/dispatch/routing_test.rb +++ b/actionpack/test/dispatch/routing_test.rb @@ -1994,6 +1994,24 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest assert_equal 'cards#destroy', @response.body end + def test_shallow_deeply_nested_resources + draw do + resources :blogs do + resources :posts do + resources :comments, shallow: true + end + end + end + + get '/comments/1' + assert_equal 'comments#show', @response.body + + assert_equal '/comments/1', comment_path('1') + assert_equal '/blogs/new', new_blog_path + assert_equal '/blogs/1/posts/new', new_blog_post_path(:blog_id => 1) + assert_equal '/blogs/1/posts/2/comments/new', new_blog_post_comment_path(:blog_id => 1, :post_id => 2) + end + def test_shallow_nested_resources_within_scope draw do scope '/hello' do |