最近自分がResolveしたrailsコードのRollbarが教えてくれたエラーについてまとめ

## この記事で書くこと
滑り込んで書き始めたアドベントカレンダー用の記事。
基本的に自分の所属するチームではRollbarのエラーは、クリティカルなもの以外は対応があまりできてなかったが、
ここ1ヶ月半ほど個人的にトライとして意識的にRollbarから通知を受けたエラーを修正するべく他のタスクとの工数を調整しつつ仕事した結果どんなエラーがあったのかまとめる。
基本的に全てコードレビューを通してる以上、どんなところがレビューでキャッチできなかったエラーだったのかということも、見直すとわかるはず。

## 参照できなくなる可能性のある値への参照が残っていた場合のundefindエラー

### ケース１
#### 背景
sidekiqにまかせているメール送信処理で、キューが溜まり実際に処理が呼ばれるまでの間にMailerに記載されている処理で参照している値が削除された。
Mailerで送信する先がユーザの登録したメールアドレスだったが、キューに溜まってる間に退会処理をされてしまうと、登録しているアカウント情報を **物理削除** している為、参照エラーが発生していた。
レアなケースだが気持ち悪かったので修正した。

#### 対応内容

Mailerで後続処理が呼ばれないよう早期リターンさせた。

```.rb
return unless user_account.present?
```


### ケース2
#### 背景
Oauth認証の処理で、必ず値が入る事が保証されない変数を読み込んでいた。

#### 対応内容
エラーを起こすのではなく、別のページ（次のアクションを促すような）に飛ばす事が望ましかった為、 `&.(ボッチ演算子)` で `nil` を許容するようにして、その場合にどう処理を続けるべきかを決めて対応

```.rb
case origin&.path
when nil
  # ここにどうすべきかの処理を記載
when '/hoge'
```

### ケース３
#### 背景
関連モデルの `after_create` で呼ばれる処理に時間がかかる事があり、たまに後続する処理の変数参照が間に合わなず、id（プライマリーキー）への参照エラーが起きていた。

```.rb
# 記載されているコードはイメージを残す為の例です。実際に動いてるコードではありません。
# roomとtopicは1対1. topicとdiscussionsは1対多
room = Room.find_or_initialize_by(user: current_user, name: 'もくもくテスト部屋')
if room.new_record?
  room.save!
  topic = Topic.new(room: room)
  topic.discussions.build(comment: comment, user: current_user)
  topic.save!  # discussionsのafter_createの非同期処理に時間がかかる時があり、
else
  topic = room.topic
end
redirect_to topic_path(topic) # ここでtopic.id への参照エラーが発生
```

#### 対応内容
Transactionで囲って、レコードの作成状態を保証しつつ、関連モデルとは別に、参照されている変数を先にcreateしてしまうようにした。

```.rb
room = Room.find_or_initialize_by(user: current_user, name: 'もくもくテスト部屋')
topic = if room.new_record?
          create_topic_with(room, comment)
        else
          room.topic
        end
redirect_to topic_path(topic)

def create_topic_with(room, comment)
  # どこかで例外が発生したとしても、Transactionで囲っているのでRollbackされるので安全。
  Room.transaction do
    room.save!
    topic = Topic.create!(room: room) # 先にcreateしてしまうようにした
    topic.discussions.build(comment: comment, user: current_user)
    topic.save!
    topic
  end
end
```


## ActiveRecord::RecordNotUniqueの発生

#### 背景
```.rb
ActiveRecord::RecordNotUnique: Mysql2::Error: Duplicate entry '1-2' for key 'index_seller_id_and_item_id'
```

のようなエラー。

sellerテーブル

```
id
name
```
itemテーブル

```
id
name
```

priceテーブル

```
seller_id
item_id
amount
indexが、index_seller_id_and_item_id の値で貼ってある。
```

この状態の時に、csv経由でbulk importにて作成する処理があり、
しかもそのcsvの内容に複数のseller_idとitem_idの重複したセットがあったときに発生

```.csv
seller_id, item_id, amount
1, 1, 100
1, 1, 120
```

のような感じ。

```.rb
new_prices = []
# csvの値を元に既存のレコードかもしくは作成かをする。
price = Price.find_or_initialize_by(seller_id: seller_id, item_id: item_id)
if price.new_record?
  new_prices << price
else
  # 省略
end

# 読み込み処理が終われば、以下でインサート
Price.import new_prices unless new_prices.empty? # <---ここでActiveRecord::RecordNotUniqueが発生
```

#### 対応内容

この場合の処理はどうしたらいいか悩んだが、sellerはitemに対し一つのpriceしか付けられない為、
csvで後ろの列に記述された処理を元にレコードを作成するように修正をした。



```.rb
new_prices = []
# csvの値を元に既存のレコードかもしくは作成かをする。
price = Price.find_or_initialize_by(seller_id: seller_id, item_id: item_id)
if price.new_record?
  new_prices.unshift(price) # uniqは一番最初にマッチしたものを残す為、常にunshiftで先頭に追加していく。
  new_prices.uniq! { |p| [p.item_id, p.seller_id] }
else
  # 省略
end
```


## 振り返って思う事
一番最後のエラーに関しては実装する時にきっちりとcsvで入れられる値についてのケースをテストで書いていたら防げたかもしれないと思うが、それ以外のエラーについてはプルリクエストのレビューで防げたというイメージがあまりない。
どうしてもエラーは出るが、出たらすぐに直す、というスタンスは間違ってないと信じたい。
最後になりましたが、Rollbar、いつもありがとう。これからもエラー通知よろしくお願いします :bow: 

それでは皆さん、良いお年を。 🐈🌟