この間書いたこの記事のコードがDRYじゃないので少し修正した。

GrapeのAPIのエンドポイントをrake routes的に出力する - ぴよログGrapeのAPIのエンドポイントをrake routes的に出力する - ぴよログ

やりたかったのは記事タイトルの通りで、Grapeで定義したAPIの結果をrake routesの結果と一緒に出力するというもの。

ソースを深く読んで行くと既存のrake routesタスクの乗っ取りはそう簡単にはいかなかったため、別のタスクでrake routesと同じような処理をした上、さらにGrapeの情報も出力するってことをやっていた。

この、rake routesと同じような処理の書き方がまずくて、このタスクの該当部分をそのまま持ってくるといういけていない書き方をしてしまっていた。

1task my_routes: :environment do
2  # この4行はRailsの中からほぼコピペしている
3  all_routes = Rails.application.routes.routes
4  require 'action_dispatch/routing/inspector'
5  inspector = ActionDispatch::Routing::RoutesInspector.new(all_routes)
6  output =  inspector.format(ActionDispatch::Routing::ConsoleFormatter.new, ENV['CONTROLLER'])
7
8  # このあとoutput を使って何かする、みたいな。
9end

これはDRYの原則に反するし、気持ち悪い。そこでなんとかする方法を考えた。

まず、このmy_routesタスクからデフォルトのroutesタスクを呼ぶことを考えた。これはとても簡単で、Rake::Task["routes"].executeというコードで呼び出すことができる。

これでできたと思いたいところだが、実はこのroutesタスク、内部で出力用データを作ってそのまま標準出力にputsしている。my_routesでやりたかったのはroutesの結果を受け取って、その内容を元に出力テキストの調整をするのでこれでは困る。

まあきっとRubyだから標準出力乗っ取るぐらい余裕だろうと思ったが、その通りだった。

capture_io

minitestの中にcapture_ioというメソッドがあって、これを使うと標準出力と標準エラーを則って文字列として取り出すことができる。

module Minitest::Assertions - minitest-5.3.4 Documentation

使い方

まず使い方から。

1task my_routes: :environment do
2  out, err = capture_io do
3    Raks::Task["routes"].execute
4  end
5
6  # このあとout を使って何かする、みたいな。
7end

capture_ioのソース

このためだけにminitestをrequireするのはどうかと思ったのでcapture_ioのコードは適当に貼り付けた。ああ。またDRYじゃない。

ソースはこんな感じ。

 1# File lib/minitest/assertions.rb, line 399
 2def capture_io
 3  _synchronize do
 4    begin
 5      require 'stringio'
 6
 7      captured_stdout, captured_stderr = StringIO.new, StringIO.new
 8
 9      orig_stdout, orig_stderr = $stdout, $stderr
10      $stdout, $stderr         = captured_stdout, captured_stderr
11
12      yield
13
14      return captured_stdout.string, captured_stderr.string
15    ensure
16      $stdout = orig_stdout
17      $stderr = orig_stderr
18    end
19  end
20end