この間書いたこの記事のコードがDRYじゃないので少し修正した。
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