【问题标题】:How to update attachment in ActiveStorage (Rails 5.2)如何在 ActiveStorage (Rails 5.2) 中更新附件
【发布时间】:2018-02-02 19:58:35
【问题描述】:

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

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

【问题讨论】:

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


    【解决方案1】:

    错误原因

    此错误是由您的模型和附件记录之间的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 关系保持一致是有意义的,最好让开发人员在附加新记录之前明确清除原始记录而不是自动执行(这可能是有点冒昧)。

    资源:

    【讨论】:

    • 感谢您的详细解答。
    • 这个答案的同一天提交解决了这个问题:github.com/rails/rails/commit/…
    • Carlos,我遇到了同样的错误 我有一个拥有个人资料的用户,并且个人资料 has_one_attach :avatar 但是,我遇到了同样的错误。我正在这样做创建方法吗? def create @profile = current_user.create_profile(profile_params) end
    • https://stackoverflow.com/questions/52469191/activemodelunknownattributeerror-unknown-attribute-avatar-activestorage
    • 很好地解释了先生。 ty
    【解决方案2】:

    使用has_one_attached时,可以在attach之前调用purge_later

    user.avatar.purge_later
    user.avatar.attach(params[:file])
    

    更新

    Rails now purges previous attachment automatically (since Aug 29th).

    【讨论】:

      【解决方案3】:

      我在保存图像时遇到了同样的问题。我希望这会有所帮助

      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
      

      【讨论】:

      • 但是如果我想这样做怎么办def create @profile = current_user.create_profile(profile_params) end
      • 所以我假设您在 user.rb 模型中有一个名为 create_profile 的方法,并且您已经粘贴了参数,例如:self.profile.attach(params[:post][:profile])。并查看current_userdevise gem 的辅助方法还是实际的user 对象。
      • stackoverflow.com/questions/52469191/… 这是我遇到的问题的帖子。我相信这是因为我传递附件的方式
      【解决方案4】:

      如果您使用嵌套属性并且子模型中没有其他属性发生更改,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,...)。 我不知道这种方法在没有嵌套属性的情况下是否有效,但我想它可以。 一般来说,这种逻辑不应该像许多人建议的那样在控制器内部处理(在我看来)。

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

      【讨论】:

      猜你喜欢
      • 2019-06-12
      • 2018-09-28
      • 2019-05-19
      • 1970-01-01
      • 2018-09-12
      • 1970-01-01
      • 1970-01-01
      • 2019-04-23
      • 1970-01-01
      相关资源
      最近更新 更多