LoginSignup
13
8

More than 3 years have passed since last update.

【Rails6】ActiveStorage::Blob#purge すると FrozenError

Last updated at Posted at 2020-02-07

環境

  • 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になったのか? :thinking:

調査してみると既にissueがありました。
ActiveStorage has_one_attached not purging attachments · Issue #37069 · rails/rails

以下のコメントに原因がめちゃめちゃ詳しく書いてありました。

https://github.com/rails/rails/issues/37069#issuecomment-525972179
image.png

今回の例に合わせて要約すると、

  • 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

異常系のテストもしっかりしとかないとダメですね。

13
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
8