Skip to content
This repository
tree: d54bc032c9
Fetching contributors…

Cannot retrieve contributors at this time

file 241 lines (196 sloc) 6.134 kb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241
require 'delegate'
require 'active_support/core_ext/string/strip'

module ActionDispatch
  module Routing
    class RouteWrapper < SimpleDelegator
      def endpoint
        rack_app ? rack_app.inspect : "#{controller}##{action}"
      end

      def constraints
        requirements.except(:controller, :action)
      end

      def rack_app(app = self.app)
        @rack_app ||= begin
          class_name = app.class.name.to_s
          if class_name == "ActionDispatch::Routing::Mapper::Constraints"
            rack_app(app.app)
          elsif ActionDispatch::Routing::Redirect === app || class_name !~ /^ActionDispatch::Routing/
            app
          end
        end
      end

      def verb
        super.source.gsub(/[$^]/, '')
      end

      def path
        super.spec.to_s
      end

      def name
        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
          reqs += " #{constraints.to_s}" unless constraints.empty?
          reqs
        end
      end

      def controller
        requirements[:controller] || ':controller'
      end

      def action
        requirements[:action] || ':action'
      end

      def internal?
        controller.to_s =~ %r{\Arails/(info|mailers|welcome)} || path =~ %r{\A#{Rails.application.config.assets.prefix}\z}
      end

      def engine?
        rack_app && rack_app.respond_to?(:routes)
      end
    end

    ##
    # This class is just used for displaying route information when someone
    # executes `rake routes` or looks at the RoutingError page.
    # People should not use this class.
    class RoutesInspector # :nodoc:
      def initialize(routes)
        @engines = {}
        @routes = routes
      end

      def format(formatter, filter = nil)
        routes_to_display = filter_routes(filter)

        routes = collect_routes(routes_to_display)

        if routes.none?
          formatter.no_routes
          return formatter.result
        end

        formatter.header routes
        formatter.section routes

        @engines.each do |name, engine_routes|
          formatter.section_title "Routes for #{name}"
          formatter.section engine_routes
        end

        formatter.result
      end

      private

      def filter_routes(filter)
        if filter
          @routes.select { |route| route.defaults[:controller] == filter }
        else
          @routes
        end
      end

      def collect_routes(routes)
        routes.collect do |route|
          RouteWrapper.new(route)
        end.reject do |route|
          route.internal?
        end.collect do |route|
          collect_engine_routes(route)

          { name: route.name,
            verb: route.verb,
            path: route.path,
            reqs: route.reqs,
            regexp: route.json_regexp }
        end
      end

      def collect_engine_routes(route)
        name = route.endpoint
        return unless route.engine?
        return if @engines[name]

        routes = route.rack_app.routes
        if routes.is_a?(ActionDispatch::Routing::RouteSet)
          @engines[name] = collect_routes(routes.routes)
        end
      end
    end

    class ConsoleFormatter
      def initialize
        @buffer = []
      end

      def result
        @buffer.join("\n")
      end

      def section_title(title)
        @buffer << "\n#{title}:"
      end

      def section(routes)
        @buffer << draw_section(routes)
      end

      def header(routes)
        @buffer << draw_header(routes)
      end

      def no_routes
        @buffer << <<-MESSAGE.strip_heredoc
You don't have any routes defined!

Please add some routes in config/routes.rb.

For more information about routes, see the Rails guide: http://guides.rubyonrails.org/routing.html.
MESSAGE
      end

      private
        def draw_section(routes)
          header_lengths = ['Prefix', 'Verb', 'URI Pattern'].map(&:length)
          name_width, verb_width, path_width = widths(routes).zip(header_lengths).map(&:max)

          routes.map do |r|
            "#{r[:name].rjust(name_width)} #{r[:verb].ljust(verb_width)} #{r[:path].ljust(path_width)} #{r[:reqs]}"
          end
        end

        def draw_header(routes)
          name_width, verb_width, path_width = widths(routes)

          "#{"Prefix".rjust(name_width)} #{"Verb".ljust(verb_width)} #{"URI Pattern".ljust(path_width)} Controller#Action"
        end

        def widths(routes)
          [routes.map { |r| r[:name].length }.max,
           routes.map { |r| r[:verb].length }.max,
           routes.map { |r| r[:path].length }.max]
        end
    end

    class HtmlTableFormatter
      def initialize(view)
        @view = view
        @buffer = []
      end

      def section_title(title)
        @buffer << %(<tr><th colspan="4">#{title}</th></tr>)
      end

      def section(routes)
        @buffer << @view.render(partial: "routes/route", collection: routes)
      end

      # the header is part of the HTML page, so we don't construct it here.
      def header(routes)
      end

      def no_routes
        @buffer << <<-MESSAGE.strip_heredoc
<p>You don't have any routes defined!</p>
<ul>
<li>Please add some routes in <tt>config/routes.rb</tt>.</li>
<li>
For more information about routes, please see the Rails guide
<a href="http://guides.rubyonrails.org/routing.html">Rails Routing from the Outside In</a>.
</li>
</ul>
MESSAGE
      end

      def result
        @view.raw @view.render(layout: "routes/table") {
          @view.raw @buffer.join("\n")
        }
      end
    end
  end
end
Something went wrong with that request. Please try again.