この間書いたこの記事のコードがDRYじゃないので少し修正した。
GrapeのAPIのエンドポイントをrake routes的に出力する - ぴよログ
やりたかったのは記事タイトルの通りで、Grapeで定義したAPIの結果をrake routesの結果と一緒に出力するというもの。
ソースを深く読んで行くと既存のrake routesタスクの乗っ取りはそう簡単にはいかなかったため、別のタスクでrake routesと同じような処理をした上、さらにGrapeの情報も出力するってことをやっていた。
この、rake routesと同じような処理の書き方がまずくて、このタスクの該当部分をそのまま持ってくるといういけていない書き方をしてしまっていた。
task my_routes: :environment do
# この4行はRailsの中からほぼコピペしている
all_routes = Rails.application.routes.routes
require 'action_dispatch/routing/inspector'
inspector = ActionDispatch::Routing::RoutesInspector.new(all_routes)
output = inspector.format(ActionDispatch::Routing::ConsoleFormatter.new, ENV['CONTROLLER'])
# このあとoutput を使って何かする、みたいな。
end
これは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
使い方
まず使い方から。
task my_routes: :environment do
out, err = capture_io do
Raks::Task["routes"].execute
end
# このあとout を使って何かする、みたいな。
end
capture_ioのソース
このためだけにminitestをrequireするのはどうかと思ったのでcapture_ioのコードは適当に貼り付けた。ああ。またDRYじゃない。
ソースはこんな感じ。
# File lib/minitest/assertions.rb, line 399
def capture_io
_synchronize do
begin
require 'stringio'
captured_stdout, captured_stderr = StringIO.new, StringIO.new
orig_stdout, orig_stderr = $stdout, $stderr
$stdout, $stderr = captured_stdout, captured_stderr
yield
return captured_stdout.string, captured_stderr.string
ensure
$stdout = orig_stdout
$stderr = orig_stderr
end
end
end