patorashのブログ

方向性はまだない

rake taskにて引数チェックでnextを使っていたのをabortに直した

rake taskではreturnが使えないのは知っていたので、引数チェックに引っかかったらnextを使っていたのだけど、それだと次の処理に移動してしまうし、終了コードが0となって正常終了扱いされてしまうなと気付きました(今更かよって感じですが)😅

じゃあexit(1)で終了させるのがいいんかな?と思っていたら、abortという便利なやつがあることを知りました。

docs.ruby-lang.org

abortを使うと、エラーコード1固定、標準エラー出力にメッセージを出力することができます。

abortを使う

修正前

nextを使っていた頃。引数bar_idがなければ、このタスクはスキップされるけれど、次のタスクがあった場合は実行されてしまうし、終了コードが0になる。

task :foo, ['bar_id'] => :environment do |task, args|
  if args.bar_id.blank?
    puts "Please set bar_id."
    next
  end

  # 続きの処理
end

修正後

abortに修正したもの。引数bar_idがなければ、このタスクで終了するし、終了コードが1になる。

task :foo, ['bar_id'] => :environment do |task, args|
  abort "Please set bar_id." if args.bar_id.blank?

  # 続きの処理
end

abortを使う際の注意点

便利なabortですが、扱いには注意が必要です。

例外処理で明示的にrescueする必要がある

rescueで特に指定しない場合、StandardErrorを継承した例外しか拾わないのでabortすると無視されます。なので、rescue SystemExit => errは必須。

私の場合は、rake taskの実行ログをDBに保存するようにしていたのですが、例外が発生した場合は結果をfalseとして保存するようにしていたのにabortを使ったら結果がtrueのまま保存されていて気づきました。なのでその実行ログを保存する処理では、以下のように修正。

begin
  yield
rescue SystemExit => err
  unless err.success?
    set_error_details(err)
  end
  raise err
rescue => err
  set_error_details(err)
  raise err
ensure
  save
end

SystemExitはexit(0)でも発生します。しかし、exit(0)が呼ばれたか、abortが呼ばれたかは、success?メソッドで判定できます。abortを使った場合はfalseになるので、これでエラーとみなして処理するわけです。

RSpecまで止まる

これは超重要なのですが、abortを使っていると、テストを流すとabortのところでテストも停止します😨。最初ビックリしましたが、わからなくもない…😔

対処方法は、.to raise_error SystemExitを必ず使って検証することです。 以下のコードのようにします。(gem 'rake_shared_context'を使用しています)

RSpec.describe 'foo' do
  include_context 'rake'
  context '引数 bar_idがない場合' do
    it 'エラーになること' do
      expect {
        subject.invoke
      }.to raise_error SystemExit
    end
  end
end

これで、テストを流してもそこでabortせずに全てのテストが実行されるようになりました👍