Railsでの例外処理(エラー処理)

エラーをrescueする場合の共通処理をメソッド化する

class ErrorUtility
  def self.log_and_notify(e)
    Rails.logger.error e.class
    Rails.logger.error e.message
    Rails.logger.error e.backtrace.join("\n")
    Bugsnag.notify e
  end
end
def self.send_daily_summary_to_all_users
  User.all.each do |user|
    begin
      UserMail.daily_summary(user).deliver
    rescue => e
      ErrorUtility.log_and_notify e
    end
  end
end

ref: https://qiita.com/jnchito/items/3ef95ea144ed15df3637#%E6%A5%AD%E5%8B%99%E3%82%A8%E3%83%A9%E3%83%BC%E3%81%AE%E5%A0%B4%E5%90%88

APIから返ってきたエラーを自分でシステムエラーに変換する

API処理はエラーが頻発する可能性があるので、エラー処理を必ず書こう!

# 外部APIを利用して課金を実行する
def execute_charge!
  response = PaymentApi.charge(self)
  error_code = response['ErrCode']
  
  # エラーコードが"0"以外であれば何らかのエラーが発生している
  if error_code != '0'    
    # 課金に失敗したのでシステムエラーを発生させる
    # エラーには必ず調査やデバッグに役立つ情報を含める
    raise "Charge failed. ErrCode: #{error_code} / ErrMessage: #{response['ErrMessage']}"
  end
  
  response['TranID']
end

エラー処理も必ずテストする

def self.send_daily_summary_to_all_users
  User.all.each do |user|
    begin
      UserMail.daily_summary(user).deliver
    rescue => e
      # "notify"をtypoしているために新たなエラーが発生し、
      # 元のエラー内容が失われる!!
      ErrorUtility.log_and_notfy e
    end
  end
end
describe User do
  describe '::send_daily_summary_to_all_users' do
    before do
      User.create!(name: 'Alice', email: 'alice@example.com')
    end
    it 'エラー処理が正しく動作すること' do
      # daily_summaryを呼び出したときにわざとエラーを発生させる
      allow(UserMail).to receive(:daily_summary).and_raise('For test')
      
      # エラーの共通処理が呼び出されることを検証する
      expect(ErrorUtility).to receive(:log_and_notify).once
      
      User.send_daily_summary_to_all_users
    end
  end
end

特定の種類のエラーだけを補足する方法

エラーを見境なく全てキャッチするアンチパターン

# NG!!
def search_tweets(keyword)
  twitter_client.search(keyword)
rescue
  # レートリミットエラー以外のエラー(たとえば認証エラー)が
  # 起きた場合も同じように捕捉されてしまう
  ['レートリミットの上限に達しました。']
end

特定のエラーのみ捕捉する

def search_tweets(keyword)
  twitter_client.search(keyword)
rescue Twitter::Error::TooManyRequests
  # レートリミットエラーだけが捕捉される
  ['レートリミットの上限に達しました。']
end