環境
- Ruby: 2.6.3
- Rails: 6.0.2.1
入力エラー時の purge でエラーが発生するようになった
こんな感じの has_one_attached の項目を持つmodelで、
post.rb
class Post < ApplicationRecord
has_one_attached :image
# 他にもいくつか項目・・
end
入力エラー時に has_one_attached の項目を purge するとエラーが発生しました。
※Rails5.2.4.1までは正常に動作していました
posts_controller.rb
def create
Post.create!(post_params)
rescue ActiveRecord::RecordInvalid => e
post = e.record
post.image&.purge # <= ここでエラー!!
end
エラーメッセージはこんな感じでした。
FrozenError
can't modify frozen Hash
ほう、Rails6からFrozenになったのか?
調査してみると既にissueがありました。
ActiveStorage has_one_attached
not purging attachments · Issue #37069 · rails/rails
以下のコメントに原因がめちゃめちゃ詳しく書いてありました。
https://github.com/rails/rails/issues/37069#issuecomment-525972179
今回の例に合わせて要約すると、
-
ActiveStorage::Blob#purge
は、blobレコード(imageの情報を持っているレコード)を destroy し、そのレコードの項目をFreezeします。
/activestorage/app/models/active_storage/blob.rb
# https://github.com/rails/rails/blob/v6.0.2.1/activestorage/app/models/active_storage/blob.rb#L232-L239
def purge
destroy # <= ココ!!
delete
rescue ActiveRecord::InvalidForeignKey
end
- その後、
delete
で保存されたファイルを削除します。
/activestorage/app/models/active_storage/blob.rb
# https://github.com/rails/rails/blob/v6.0.2.1/activestorage/app/models/active_storage/blob.rb#L227-L230
def delete
service.delete(key) # <= ココ!!
service.delete_prefixed("variants/#{key}/") if image?
end
-
ActiveStorage::Blob#delete
は#key
を呼び出します。 Postレコードが保存されていないため、blobにはキーがありません。#key
は、blob にまだキーがない場合にキーを生成しようとします。 生成されたキーをblobのFreezeされた項目に設定しようとするとエラー発生します。
/activestorage/app/models/active_storage/blob.rb
# https://github.com/rails/rails/blob/v6.0.2.1/activestorage/app/models/active_storage/blob.rb#L115-L118
def key
# We can't wait until the record is first saved to have a key for it
self[:key] ||= self.class.generate_unique_secure_token
# ^ self[:key]が無いので右辺が実行されるが、selfの項目はFreeze済みなので FrozenError!!
end
原因はそんなところです。
回避策としては、 #purge
を呼ぶ代わりに nil
を設定すればOKです。
posts_controller.rb
def create
Post.create!(post_params)
rescue ActiveRecord::RecordInvalid => e
post = e.record
post.image = nil # <= purge を呼ぶ代わりに nil 設定で回避
end
異常系のテストもしっかりしとかないとダメですね。