# Rails での例外処理 2

Rails での例外処理については、以前(2 日前)にも 書いていましたが、少し変更しました。

これは、管理画面でユーザーをインポートするときの処理です。

lib じゃなくて、app/formsformオブジェクト として作れば良かった。

ここに載せた実装は、多分良くない実装です。。

# lib/csv_reader.rb

module CsvReader
  require 'csv'

  def import_user(csv_path)
    read_csv = CSV.read(csv_path, headers: true)
    ActiveRecord::Base.transaction do
      read_csv.each do |csv|
        user = User.find_or_initialize_by(id: csv['ID'])
        user.update!(user_attr(csv))
      end
    end
    true
  rescue ActiveRecord::RecordInvalid => e
    error_messages = e.record.errors.full_messages.map { |error| "ID: #{e.record.id}#{error}" }
    Rails.logger.debug error_messages
    error_messages
  rescue ActiveRecord::RecordNotFound, ActiveHash::RecordNotFound => e
    match = e.message.match(/^Couldn't find (\w+)/)
    model_name = I18n.t('activerecord.models.' + match[1].underscore)
    error_messages = "#{model_name} が間違っています"
    Rails.logger.debug error_messages
    error_messages
  end

  def user_attr(csv)
    {
      id: csv['ID'],
      first_name: csv['名前'],
      last_name: csv['苗字'],
      age: csv['年齢'],
      job: Job.find_by!(name: csv['仕事']),
      company: Company.find_by!(name: csv['会社'])
    }
  end
end

# ActiveRecord::RecordInvalid

ActiveRecord::RecordInvalid は、 バリデーションに引っかかったときのエラーです。

# ActiveRecord::RecordNotFound

find_by: 見つからなかったときに nil を返す。

find_by!: 見つからなかったときに ActiveRecord::RecordNotFound を返します。

# ActiveHash::RecordNotFound

ActiveHash::RecordNotFoundActiveHash 版の ActiveRecord::RecordNotFound です。

ActiveHash は、active_hash という gem を使って、 ActiveRecord のように区分値情報を扱えるものです。 説明しづらいのですが便利です。

# app/controllers/users_controller.rb

# frozen_string_literal: true

class UsersController < ApplicationController
  ...

  def import
    csv_file = params[:csv_file]

    if csv_file.present?
      if csv_file.original_filename.include?('.csv')
        @user = CsvReader.import_user(csv_file.path)
        if @user == true
          flash[:success] = 'インポートに成功しました'
        else
          flash[:danger] = ['インポートに失敗しました', *@user]
        end
      else
        flash[:danger] = 'CSVファイル以外は対応していません'
      end
    else
      flash[:danger] = 'ファイルが選択されていません'
    end
    redirect_to new_user_path
  end
end

# original_filename

original_filename で、ファイル名を取れるので csv かどうか判別している。

# Rails での例外処理を調べてみて

プロを目指す人のための例外処理(再)入門 / #rubykansai 2018-01-13 - Speaker Deck を読んで、例外処理を全然わかってないことに気づきました。

上に書いたような処理は、業務エラー で例外処理は原則として使わない方が良い。 こういったとき、成功 or 失敗 はメソッドの戻り値で表現する。 呼び出し側は必ず戻り値を検証する。

ActiveRecord::Rollback で、明示的にロールバックさせることができる。

不必要に 例外処理 を使ってしまっていた。 例外処理 をなくして 業務エラー に対処していきたい。

ActiveRecord::RecordInvalid の部分とか、

例外処理を使わずに実装する場合のコード例 - プロを目指す人のための例外処理(再)入門 / #rubykansai 2018-01-13 - Speaker Deck

こんな感じで対応していきたいが、エラーメッセージをどうやって コントローラー側で取ればよいかまだわかっていない。

# 感想

プロを目指す人のためのRuby入門 持っているのに、読めてないので読みたい。

基本的なところが抜けているので、しっかり基礎から勉強していきたい。

# 参考