最近プロジェクトを最新のRailsバージョン(5.2)にアップグレードしてActiveStorage
を取得しました-AWS S3、Google Cloudなどのクラウドサービスへの添付ファイルのアップロードを処理するライブラリ。
ほとんどすべてが正常に動作します。画像をアップロードして添付できます
user.avatar.attach(params[:file])
そしてそれを受け取る
user.avatar.service_url
しかし今、私はユーザーのアバターを置き換え/更新したいと思います。走れると思った
user.avatar.attach(params[:file])
再び。しかし、これはエラーをスローします:
ActiveRecord::RecordNotSaved: Failed to remove the existing associated avatar_attachment. The record failed to save after its foreign key was set to nil.
それはどういう意味ですか?ユーザーのアバターを変更するにはどうすればよいですか?
このエラーは、モデルと添付レコードの間のhas_one
関連付けによって発生しています。これは、元の添付ファイルを新しいものに置き換えようとすると、元のファイルが孤立し、belongs_to
関連付けの外部キー制約が失敗するために発生します。これは、すべてのActiveRecord has_one
関係の動作です(つまり、ActiveStorageに固有ではありません)。
class User < ActiveRecord::Base
has_one :profile
end
class Profile < ActiveRecord::Base
belongs_to :user
end
# create a new user record
user = User.create!
# create a new associated profile record (has_one)
original_profile = user.create_profile!
# attempt to replace the original profile with a new one
user.create_profile!
=> ActiveRecord::RecordNotSaved: Failed to remove the existing associated profile. The record failed to save after its foreign key was set to nil.
新しいプロファイルを作成する際、ActiveRecordは元のプロファイルのuser_id
をnil
に設定しようとしますが、これはbelongs_to
レコードの外部キー制約に失敗します。 ActiveStorageを使用してモデルに新しいファイルをアタッチしようとすると、これが本質的に何が起こっていると思います...これにより、元のアタッチメントレコードの外部キーを無効にしようとしますが、失敗します。
has_one
関係の解決策は、新しいレコードを作成する前に関連するレコードを破棄することです(つまり、別のレコードをアタッチする前にアタッチメントをパージします)。
user.avatar.purge # or user.avatar.purge_later
user.avatar.attach(params[:file])
Has_one関係の新しいレコードをアタッチしようとするときにActiveStorageが元のレコードを自動的にパージするかどうかは、コアチームに提起される別の質問です...
IMOが他のすべてのhas_one関係と一貫して機能することは理にかなっており、新しいレコードを添付する前に、自動的に実行するよりも、元のレコードをパージすることを明示するように開発者に任せることが望ましい場合があります(これは少しおかしいかもしれません) )。
リソース:
purge_later
を使用する場合、attach
の前にhas_one_attached
を呼び出すことができます。
user.avatar.purge_later
user.avatar.attach(params[:file])
更新
画像の保存に関しても同じ問題があります。これが役に立てば幸い
class User < ApplicationRecord
has_one_attached :avatar
end
フォームとコントローラーを見てみましょう
= simple_form_for(@user) do |f|
= f.error_notification
.form-inputs
= f.input :name
= f.input :email
= f.input :avatar, as: :file
.form-actions
= f.button :submit
controllers/posts_controller.rb
def create
@user = User.new(post_params)
@user.avatar.attach(params[:post][:avatar])
respond_to do |format|
if @user.save
format.html { redirect_to @user, notice: 'Post was successfully created.' }
format.json { render :show, status: :created, location: @user }
else
format.html { render :new }
format.json { render json: @user.errors, status: :unprocessable_entity }
end
end
end