如何在ActiveStorage中更新附件(Rails 5.2)

时间:2017-08-24 20:03:36

标签: ruby-on-rails activerecord ruby-on-rails-5 rails-activestorage

我最近将我的项目升级到最新的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.

这是什么意思?如何更改用户头像?

4 个答案:

答案 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,...)。 我不知道这种方法是否可以在没有嵌套属性的情况下工作,但是我想可以。 通常,这种逻辑不应该在控制器内部处理(我认为)。

花了我一段时间才能弄清楚,希望对您有所帮助。干杯!