rake taskではreturnが使えないのは知っていたので、引数チェックに引っかかったらnextを使っていたのだけど、それだと次の処理に移動してしまうし、終了コードが0となって正常終了扱いされてしまうなと気付きました(今更かよって感じですが)😅
じゃあexit(1)
で終了させるのがいいんかな?と思っていたら、abort
という便利なやつがあることを知りました。
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せずに全てのテストが実行されるようになりました👍