我最近将我的项目升级到最新的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.
这是什么意思?如何更改用户头像?
答案 0 :(得分:8)
您的模型与附件记录之间的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关系一致工作是有道理的,并且可能最好让开发人员明确关于在附加新记录之前清除原始记录而不是自动执行(可能是有点放肆)。
<强>资源:强>
答案 1 :(得分:2)
使用purge_later
时,您可以在attach
之前致电has_one_attached
:
user.avatar.purge_later
user.avatar.attach(params[:file])
<强>更新强>
Rails now purges previous attachment automatically (since Aug 29th)
答案 2 :(得分:1)
我遇到了与图像保存相同的问题。我希望这会有所帮助
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
控制器/ 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
答案 3 :(得分:0)
如果您使用的是嵌套属性,并且子模型中没有其他属性发生更改,Rails不会自动检测到对附件的更改。为此,您必须重写changed_for_autosave?
方法:
def Child
belongs_to :parent
has_one_attached :attachment
# Magic happens here
def changed_for_autosave?
super || attachment.changed_for_autosave?
end
end
def Parent
has_many :children
accepts_nested_attributes_for :children
end
这还会在父级保存时触发子级模型回调(before_save
,...)。
我不知道这种方法是否可以在没有嵌套属性的情况下工作,但是我想可以。
通常,这种逻辑不应该在控制器内部处理(我认为)。
花了我一段时间才能弄清楚,希望对您有所帮助。干杯!