Skip to content

Instantly share code, notes, and snippets.

@mikong
Created March 26, 2010 06:57
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mikong/344609 to your computer and use it in GitHub Desktop.
Save mikong/344609 to your computer and use it in GitHub Desktop.
# I found this code I wrote in an old project of mine (written around Sept 2008). The project upgraded to a new Rails version and I had to find the routes with errors.
# core extensions
class Array
def bsearch_lower_boundary(key)
range = 0 ... self.length
lower = range.first - 1
upper = range.last
while lower + 1 != upper
mid = ((lower + upper) / 2).to_i # for working with mathn.rb (Rational)
if (self[mid] <=> key) < 0
lower = mid
else
upper = mid
end
end
return upper
end
def bsearch_first(key)
boundary = bsearch_lower_boundary(key)
if boundary >= self.length || (self[boundary] <=> key) != 0
return nil
else
return boundary
end
end
def permutation(n=size)
if size < n or n < 0
elsif n == 0
yield([])
else
self[1..-1].permutation(n - 1) do |x|
(0...n).each do |i|
yield(x[0...i] + [first] + x[i..-1])
end
end
self[1..-1].permutation(n) do |x|
yield(x)
end
end
end
end
# Reports invalid routes due to Rails upgrade.
# Warning: This generator currently forces user to clear routes_report.txt to make sure the generator
# won't overwrite a previous report.
class RoutesReportGenerator
ROUTES_REPORT_FILENAME = File.join(RAILS_ROOT, 'routes_report.txt')
HASH_FOR_PREFIX = 'hash_for_'
def initialize
@dirs_to_scan = ["vendor/plugins/app_core/app/views",
"vendor/plugins/app_core/app/controllers",
"vendor/plugins/app_core/app/helpers"]
@exclude_list = ["polymorphic_path", # Rails helper
"poly_tell_a_friend_url", "poly_comment_url", "full_url", "embed_url", "image_url", # variables
"infer_page_from_url", "render_path", "document_download_path", # method
"upload_url", "flash_url", # javascript properties
"new_url"] # fark.com URL parameter
@valid_named_routes = ActionController::Routing::Routes.named_routes.names.collect {|x| x.to_s}.sort
@found_named_routes = []
@error_routes = []
@fix_suggestions = []
prepare_file(ROUTES_REPORT_FILENAME)
end
def run
@dirs_to_scan.each do |dir_path|
real_dir_path = File.join(RAILS_ROOT, dir_path)
scan_directory_for_routes(real_dir_path)
end
generate_routes_report
end
private
def scan_directory_for_routes(dir)
all_files = File.join(dir, '**', '*')
Dir[all_files].each do |filename|
if File.directory?(filename)
@dirs_to_scan << filename
elsif File.file?(filename)
if File.readable?(filename)
File.open(filename) do |f|
f.each do |line|
# Note: cannot use the grouping parenthesis as it will mess up the scan
matches = line.scan(/[^a-z.:_@]([a-z_]+_path|[a-z_]+_url)[^a-z_]/).flatten
matches.each do |match|
check_named_route(match)
end
end
end
else
# display warning: an unreadable file cannot be processed
end
end
end
end
def check_named_route(named_route)
unless @found_named_routes.include?(named_route) || @exclude_list.include?(named_route)
@found_named_routes << named_route
unless valid_named_route?(named_route)
@error_routes << named_route
@fix_suggestions << suggest_valid_names(named_route)
end
end
end
def valid_named_route?(named_route)
base_named_route = named_route.chomp("_path").chomp("_url")
if @valid_named_routes.bsearch_first(base_named_route).nil?
if base_named_route.starts_with?(HASH_FOR_PREFIX) &&
!@valid_named_routes.bsearch_first(base_named_route[HASH_FOR_PREFIX.length, base_named_route.length-1]).nil?
return true
else
return false
end
else
return true
end
end
def generate_routes_report
File.open(ROUTES_REPORT_FILENAME, 'w') do |output_file|
output_file.puts "OK Routes:"
ok_routes = @found_named_routes - @error_routes
ok_routes.each do |named_route|
output_file.puts named_route
end
output_file.puts "\nRoutes with errors:" unless @error_routes.empty?
no_suggestion_count = 0
@error_routes.each_with_index do |named_route, index|
output_file.print named_route
if @fix_suggestions[index].blank?
no_suggestion_count += 1
else
output_file.print " -> #{@fix_suggestions[index]}"
end
output_file.print "\n"
end
output_file.puts "\nInvalid named routes with no fix suggestion: #{no_suggestion_count}"
end
end
def prepare_file(filename)
raise("A non-empty #{filename} exists!") if FileTest::exists?(filename) && !(FileTest::zero?(filename))
f = File.new(filename, 'w')
f.close
end
def suggest_valid_names(named_route)
words = named_route.split('_')
# pops _path or _url out of the words
suffix = words.pop
suggestions = []
words.uniq.permutation do |p|
key = p.join('_')
unless @valid_named_routes.bsearch_first(key).nil?
suggestions << "#{key}_#{suffix}"
end
end
suggestions.join(' ')
end
end
# Notes:
# 1. script/console errors
# 2. Design script
# 3. Use Dir[].each instead of Dir.each
# 4. Scan all matches instead of 1 match per line
# 5. Use collections instead of files. Only use file for final report.
# 6. Using eval("app.#{named_route}") won't work,
# use ActionController::Routing::Routes.named_routes.names
# 7. Convert to rake task
# 8. Add option to manually list strings to be excluded from the matching?
# a. exclude: polymorphic_path
# 9. Suggest alternative path names
# a. permutations of invalid named routes are checked against the list of valid ones
# b. "hash_for_", try removing hash_for <--
# 10. Handle instance variables by matching only named routes not preceded by @
# Weird: name_prefix => nil is not working for nested map.resources
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment