ActiveStorageとバリデーションエラーの挙動

- ActiveStorage 5.2

##　モデルの定義

こんなモデルがあった時のバリデーションエラーの挙動

```ruby
class Archive
  has_one_attached :zip

  validates :name, presence: true
  validate :zip_should_be_presence

  # https://github.com/rails/rails/issues/31656
  def zip_should_be_presence
    if zip.attached?
      if !blob_is_zip?
        zip.purge # ActiveStorage::Blob.unattachedでお掃除バッチを作ることを考えるとここでpurgeしなくてもいいかも。
        errors.add(:zip, 'はZIPではありません')
      end
    else
      errors.add(:zip, :presence)
    end
  end

  def blob_is_zip?
    zip.blob.content_type == 'application/zip' && zip.blob.filename.extension_without_delimiter == 'zip'
  end
end
```

### 添付ファイルでバリデーションエラー

ファイル名を`hoge.jpg`にしている。今回のモデルの定義では`purge`を実行するようにしているので削除される。

```irb
irb(main):007:0> Archive.create!(name: 'hoge', zip: {io: File.open('hoge.zip'), filename: 'hoge.jpg'})
  Disk Storage (4.1ms) Uploaded file to key: gvRhRYR6kYopMacbhdbATQQH (checksum: mTlNhQT67kf1chY3Gdtydg==)
   (0.1ms)  begin transaction
  ActiveStorage::Blob Create (1.4ms)  INSERT INTO "active_storage_blobs" ("key", "filename", "content_type", "metadata", "byte_size", "checksum", "created_at") VALUES (?, ?, ?, ?, ?, ?, ?)  [["key", "gvRhRYR6kYopMacbhdbATQQH"], ["filename", "hoge.jpg"], ["content_type", "application/zip"], ["metadata", "{\"identified\":true}"], ["byte_size", 326], ["checksum", "mTlNhQT67kf1chY3Gdtydg=="], ["created_at", "2018-04-19 05:19:41.046491"]]
   (1.6ms)  commit transaction
   (0.0ms)  begin transaction
   (0.1ms)  commit transaction
   (0.0ms)  begin transaction
  Disk Storage (0.2ms) Deleted file from key: gvRhRYR6kYopMacbhdbATQQH
  ActiveStorage::Blob Destroy (0.6ms)  DELETE FROM "active_storage_blobs" WHERE "active_storage_blobs"."id" = ?  [["id", 48]]
   (0.6ms)  rollback transaction
Traceback (most recent call last):
        1: from (irb):7
ActiveRecord::RecordInvalid (バリデーションに失敗しました。 ZipはZIPではありません)
```

### `Archive`モデル側でバリデーションエラー
blobは問題ないので特に削除されない。

```irb
irb(main):003:0> Archive.create!(zip: {io: File.open('hoge.zip'), filename: 'hoge.zip')
  Disk Storage (4.5ms) Uploaded file to key: rYvGGj9FDYDvgcrCyqn3oVUs (checksum: mTlNhQT67kf1chY3Gdtydg==)
   (0.1ms)  begin transaction
  ActiveStorage::Blob Create (1.5ms)  INSERT INTO "active_storage_blobs" ("key", "filename", "content_type", "metadata", "byte_size", "checksum", "created_at") VALUES (?, ?, ?, ?, ?, ?, ?)  [["key", "rYvGGj9FDYDvgcr
Cyqn3oVUs"], ["filename", "hoge.zip"], ["content_type", "application/zip"], ["metadata", "{\"identified\":true}"], ["byte_size", 326], ["checksum", "mTlNhQT67kf1chY3Gdtydg=="], ["created_at", "2018-04-19 05:18:43.65
2190"]]
   (1.3ms)  commit transaction
   (0.1ms)  begin transaction
   (0.1ms)  commit transaction
   (0.0ms)  begin transaction
   (0.1ms)  rollback transaction
Traceback (most recent call last):
        1: from (irb):3
ActiveRecord::RecordInvalid (バリデーションに失敗しました。 Nameを入力してください。)
```

### 迷子のblob


`unattached`というお掃除バッチ用のスコープがある。

```ruby
# https://github.com/rails/rails/pull/31970
ActiveStorage::Blob.unattached.find_each(&:purge_later)
```

ダイレクトアップロード（先に`ActiveStorage::Blob`を作っておいて、`ActiveStorage::Blob#signed_id`を使って紐付けを行う仕組み）などを使うとこういうBlobがたくさん作られるので、お掃除バッチの作成は必須ですね。

## 参考

- https://github.com/rails/rails/issues/31656
- https://github.com/rails/rails/pull/31970