web-dev-qa-db-fra.com

Comment mettre à jour la pièce jointe dans ActiveStorage (Rails 5.2)

J'ai récemment mis à niveau mon projet vers la dernière version Rails (5.2) pour obtenir ActiveStorage - une bibliothèque qui gère les téléchargements de pièces jointes vers des services cloud comme AWS S3, Google Cloud etc.).

Presque tout fonctionne bien. Je peux télécharger et joindre des images avec

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

et le recevoir avec

user.avatar.service_url

Mais maintenant, je veux remplacer/mettre à jour l'avatar d'un utilisateur. Je pensais pouvoir courir

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

encore. Mais cela jette une erreur:

ActiveRecord::RecordNotSaved: Failed to remove the existing associated avatar_attachment. The record failed to save after its foreign key was set to nil.

Qu'est-ce que cela signifie? Comment puis-je changer l'avatar d'un utilisateur?

11
zarathustra

La cause de l'erreur

Cette erreur est signalée par le has_one association entre votre modèle et l'enregistrement de pièce jointe. Cela se produit car si vous essayez de remplacer la pièce jointe d'origine par une nouvelle, celle-ci sera orpheline et provoquera l'échec de la contrainte de clé étrangère pour belongs_to les associations. C'est le comportement de tous les ActiveRecord has_one relations (c'est-à-dire qu'il n'est pas spécifique à ActiveStorage).

Un exemple analogue

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.

En tentant de créer un nouveau profil, ActiveRecord essaie de définir le user_id du profil d'origine à nil, ce qui échoue à la contrainte de clé étrangère pour belongs_to enregistrements. Je crois que c'est essentiellement ce qui se passe lorsque vous essayez de joindre un nouveau fichier à votre modèle à l'aide d'ActiveStorage ... cela essaie d'annuler la clé étrangère de l'enregistrement de pièce jointe d'origine, ce qui échouera.

La solution

La solution pour has_one relations consiste à détruire l'enregistrement associé avant d'essayer d'en créer un nouveau (c'est-à-dire purger la pièce jointe avant d'essayer d'en attacher une autre).

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

Est-ce le comportement souhaité?

La question de savoir si ActiveStorage doit automatiquement purger l'enregistrement d'origine lorsque vous essayez d'en attacher un nouveau pour les relations has_one est une question différente à poser à l'équipe principale ...

IMO le faire fonctionner de manière cohérente avec toutes les autres relations has_one est logique, et il peut être préférable de laisser au développeur le soin d'expliciter la purge d'un enregistrement original avant d'en attacher un nouveau plutôt que de le faire automatiquement (ce qui peut être un peu présomptueux). ).

Ressources:

9
Carlos Ramirez III

Tu peux appeler purge_later avant attach lors de l'utilisation de has_one_attached:

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

Mise à jour

Rails purge désormais automatiquement la pièce jointe précédente (depuis le 29 août) .

3
ybart

J'ai le même problème avec la sauvegarde d'image. J'espère que cela vous aidera

class User < ApplicationRecord
  has_one_attached :avatar
end

regardons le formulaire et le contrôleur

= 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
1
Kiry Meas