Rails Active Storage - 保留现有文件/上传?

问题描述 投票:0回答:3

我有一个 Rails 模型:

has_many_attached :files

默认情况下通过 Active Storage 上传时,如果您上传新文件,它会删除所有现有上传内容并将其替换为新文件。

我有一个控制器破解,由于多种原因,这不太理想:

Rails 6 中使用 has_many_attached 更新图像的正确方法是什么

有没有办法配置 Active Storage 以保留现有存储?

ruby-on-rails rails-activestorage
3个回答
22
投票

看起来有一个配置可以做到这一点

config.active_storage.replace_on_assign_to_many = false

不幸的是,根据当前的 Rails 源代码,它已被弃用,并将在 Rails 7.1 中被删除

config.active_storage.replace_on_assign_to_many
已弃用,并将在 Rails 7.1 中删除。在升级之前,请确保您的代码在将
config.active_storage.replace_on_assign_to_many
设置为
true
的情况下运行良好。 要将新的可附加项附加到 Active Storage 关联,请优先使用
attach
。 使用关联设置器将导致清除现有附加附件并用新附件替换它们。

看起来明确使用

attach
将是唯一的出路。

所以一种方法是在控制器中设置所有内容:

def update
  ...
  if model.update(model_params)
    model.files.attach(params[:model][:files]) if params.dig(:model, :files).present?
  else
    ...
  end
end

如果您不喜欢在控制器中包含此代码。例如,您可以覆盖模型的默认设置器,如下所示:

class Model < ApplicationModel
  has_many_attached :files

  def files=(attachables)
    files.attach(attachables)
  end
end

不确定我是否会建议这个解决方案。我更愿意添加新方法来附加文件:

class Model < ApplicationModel
  has_many_attached :files

  def append_files=(attachables)
    files.attach(attachables)
  end
end

并在您的表单中使用

  <%= f.file_field :append_files %>

它可能还需要模型中的阅读器,可能还需要一个更好的名称,但它应该演示这个概念。


5
投票

@edariedl 建议覆盖 writer 的解决方案不起作用,因为它会导致

stack level too deep

第一个解决方案

基于 ActiveStorage 源代码 在这一行

您可以覆盖

has_many_attached
的编写器,如下所示:

class Model < ApplicationModel
  has_many_attached :files

  def files=(attachables)
     attachables = Array(attachables).compact_blank

    if attachables.any?
      attachment_changes["files"] =
        ActiveStorage::Attached::Changes::CreateMany.new("files", self, files.blobs + attachables)
    end
  end
end

重构/第二个解决方案

您可以创建一个模型关注点,通过允许您指定您想要

old
行为的 has_many_attached 字段,封装所有这些逻辑并使其更加动态,同时仍为较新的
 保持新行为has_many_attached
字段,您是否应该在启用新行为后添加任何字段。

app/models/concerns/append_to_has_many_attached.rb

module AppendToHasManyAttached
  def self.[](fields)
    Module.new do
      extend ActiveSupport::Concern

      fields = Array(fields).compact_blank # will always return an array ( worst case is an empty array)

      fields.each do |field|
        field = field.to_s # We need the string version
        define_method :"#{field}=" do |attachables|
          attachables = Array(attachables).compact_blank

          if attachables.any?
            attachment_changes[field] =
              ActiveStorage::Attached::Changes::CreateMany.new(field, self, public_send(field).public_send(:blobs) + attachables)
          end
        end
      end
    end
  end
end

在你的模型中:

class Model < ApplicationModel
  include AppendToHasManyAttached['files'] # you can include it before or after, order does not matter, explanation below

  has_many_attached :files
end

注意:无论您是

prepend
还是
include
模块都没关系,因为ActiveStorage生成的方法都添加到这个生成的模块中,当您从
ActiveRecord::Base
这里

继承时,它会很早就被调用

==> 所以你的作家永远优先。

替代/最后的解决方案:

如果您想要更加动态和健壮的东西,您仍然可以创建模型关注点,但您可以像这样在模型的

attachment_reflections
内循环:

reflection_names = Model.reflect_on_all_attachments.filter { _1.macro == :has_many_attached }.map { _1.name.to_s } # we filter to exclude `has_one_attached` fields
# => returns ['files']
reflection_names.each do |name|
  define_method :"#{name}=" do |attachables|
  # ....
  end
end

但是我相信要使其起作用,您需要在对

has_many_attached
的所有调用之后包含此模块,否则它将无法工作,因为反射数组不会完全填充(每个对 has_many_attached 的调用都会附加到该模块数组)


-1
投票

从 Rails 7 开始,您可以在表单中调用

include_hidden: false
来阻止这种情况发生。

<%= form.file_field :images, multiple: true, include_hidden: false %>

有关

file_field
文档的更多详细信息。

:include_hidden - 当 multiple: true 和 include_hidden: true 时,该字段将以空值字段作为前缀,以支持提交空文件集合。

https://edgeapi.rubyonrails.org/classes/ActionView/Helpers/FormBuilder.html#method-i-file_field

© www.soinside.com 2019 - 2024. All rights reserved.