Как обновить вложение в ActiveStorage (Rails 5.2)

Недавно я обновил свой проект до последней версии 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.

Что это должно означать? Как я могу изменить аватар пользователя?


person zarathustra    schedule 24.08.2017    source источник


Ответы (4)


Причина ошибки

Эта ошибка возникает из-за связи 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])

Это желаемое поведение?

Должен ли ActiveStorage автоматически очищать исходную запись при попытке присоединить новую для отношений has_one - это другой вопрос, который лучше всего задать основной группе ...

В ИМО, что он работает согласованно со всеми другими отношениями has_one, имеет смысл, и может быть предпочтительнее оставить на усмотрение разработчика, чтобы он явно очищал исходную запись перед присоединением новой, а не делал это автоматически (что может быть немного самонадеянным ).

Ресурсы:

person Carlos Ramirez III    schedule 29.08.2017
comment
Спасибо за подробный ответ. - person zarathustra; 29.08.2017
comment
Эта фиксация, сделанная в тот же день, что и этот ответ, устраняет эту проблему: relithubrequest.com / rails / commit / - person ybart; 14.09.2017
comment
Карлос, я получаю ту же ошибку, что и у пользователя с профилем, а профиль has_one_attach :avatar Однако я получаю ту же ошибку. Я так делаю метод create? def create @profile = current_user.create_profile(profile_params) end - person Steven Aguilar; 23.09.2018
comment
Красиво объяснил, сэр. ты - person ARK; 11.08.2020

Вы можете вызвать purge_later перед attach при использовании has_one_attached:

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

Обновить

Rails теперь автоматически.

person ybart    schedule 29.08.2017

У меня такая же проблема, что и с сохранением изображений. Я надеюсь, это поможет

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
person Kiry Meas    schedule 02.02.2018
comment
но что, если я пытаюсь сделать это так def create @profile = current_user.create_profile(profile_params) end - person Steven Aguilar; 23.09.2018
comment
Итак, я предполагаю, что у вас есть метод user.rb модели под названием create_profile, и вы уже вставили параметры в метод, который вы делаете, например: self.profile.attach(params[:post][:profile]). И посмотрите на current_user, если это вспомогательный метод devise драгоценного камня или фактический user объект. - person Kiry Meas; 24.09.2018
comment
stackoverflow.com/questions/52469191/ Здесь это сообщение о проблеме, которая у меня возникла. Я верю, что это связано с тем, как я передаю прикрепленный файл - person Steven Aguilar; 24.09.2018

Если вы используете вложенные атрибуты и никакой другой атрибут в дочерней модели не изменился, 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, ...) при сохранении родительского объекта. Я не знаю, работает ли этот подход без вложенных атрибутов, но полагаю, что это так. В общем, такая логика не должна обрабатываться внутри контроллеров, как многие предлагали (на мой взгляд).

Мне потребовалось время, чтобы понять, надеюсь, это поможет. Ваше здоровье !

person Ghis    schedule 04.05.2020