rake routes の出力をハックする

この文書では、人に見やすい形に成形されて表示される（その代わりプログラムで処理しづらい） `rake routes` の出力結果を csv とかに変える方法について記します。

主に著者の環境である rails 3.2.22 を対象にしていきたいと思います。

Rails 3.2.22
----

さて、`routes` タスクそのものは `railties-3.2.22/lib/rails/tasks/routes.rake` で定義されています（ので抜粋しておきます）。

```ruby
desc 'Print out all defined routes in match order, with names. Target specific controller with CONTROLLER=x.'
task :routes => :environment do
  Rails.application.reload_routes!
  all_routes = Rails.application.routes.routes

  require 'rails/application/route_inspector'
  inspector = Rails::Application::RouteInspector.new
  puts inspector.format(all_routes, ENV['CONTROLLER']).join "\n"
end
```

タスクの前半２行をコピペして独自タスクを作るのが鮮やかそうではあります。
しかし、 `all_routes` の中身は `Journey::Routes` のインスタンスとなっており、よくわかりません。
(https://github.com/rails/rails/blob/v3.2.22/actionpack/actionpack.gemspec#L28 を見るとわかるけど、また別の依存 gem )

Journey を無視して `Rails::Application::RouteInspector` を読むと `#format_routes` という、とてもわかりやすいメソッドを見つけられるはずです。


```ruby

module Rails
  class Application
    class RouteInspector # :nodoc:
      def formatted_routes(routes)
        name_width = routes.map{ |r| r[:name].length }.max
        verb_width = routes.map{ |r| r[:verb].length }.max
        path_width = routes.map{ |r| r[:path].length }.max

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

あとはこれにパッチを当てるだけですが、 `rake routes` は Rake タスクなので、 `lib` ディレクトリのロードがどうも怪しかったりします・・・ので、 `lib/task/patch.rake` とかにこんなパッチを書けば良いでしょう。

```ruby

module RouteFormatCSVPatch
  def formatted_routes(routes)
    routes.map do |r|
      "#{r[:name]},#{r[:verb]},#{r[:path]},#{r[:reqs]}"
    end
  end
end

require 'rails/application/route_inspector'
Rails::Application::RouteInspector.send(:prepend, RouteFormatCSVPatch)
```

外部エンジンなどを使って route が足された場合などには正しい CSV にならない（外部エンジン表記が途中行に入る）ですが、まぁ、前よりかはましでしょう。
（完璧を目指すなら`#formatted_routes_for_engines` にもパッチを当てること）


Rails 4.x
----

4系では、より拡張がしやすくなっています。
同じくタスクが定義されている `railties-4.2.3/lib/rails/tasks/routes.rake` からソースを抜粋しましょう。


```ruby

desc 'Print out all defined routes in match order, with names. Target specific controller with CONTROLLER=x.'
task routes: :environment do
  all_routes = Rails.application.routes.routes
  require 'action_dispatch/routing/inspector'
  inspector = ActionDispatch::Routing::RoutesInspector.new(all_routes)
  puts inspector.format(ActionDispatch::Routing::ConsoleFormatter.new, ENV['CONTROLLER'])
end
```

フォーマッターが別クラスで用意されているので、パッチを当てるより、独自フォーマッターを定義してやる手法の方が良さそうです。

例えば、こんな感じのタスクを `lib/task/route.rake` とかに書くと良いでしょう。

```ruby

namespace :routes do

  class CSVFormatter
    def initialize
      @csv = []
      @current_section = "Application"
    end

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

    def section_title(title)
      @current_section = title
    end

    def section(routes)
      routes.each do |r|
        @buffer << "#{r[:name]},#{r[:verb]},#{r[:path]},#{r[:reqs]}"
      end
    end

    def header(routes)
      @buffer << "Prefix, Verb, URI Pattern, Controller#Action"
    end

    def no_routes
      @buffer << ""
    end
  end

  desc 'Print out all defined routes on CSV'
  task csv: :environment do
    all_routes = Rails.application.routes.routes
    require 'action_dispatch/routing/inspector'
    inspector = ActionDispatch::Routing::RoutesInspector.new(all_routes)
    puts inspector.format(CSVFormatter.new, ENV['CONTROLLER'])
  end
end
```