繁体   English   中英

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

[英]Rails Active Storage - Keep Existing Files / Uploads?

我有一个 Rails model:

has_many_attached :files

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

我从这里得到了一个 controller 黑客攻击,由于许多原因,它不太理想:

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

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

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

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后运行良好。 要将 append 新附件添加到 Active Storage 关联中,请优先使用attach 使用关联设置器将导致清除现有的附加附件并用新附件替换它们。

看起来明确使用attach将是唯一的出路。

所以一种方法是在 controller 中设置所有内容:

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

如果您不喜欢在 controller 中使用此代码。例如,您可以覆盖 model 的默认设置器,例如:

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 %>

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

@edariedl建议覆盖编写器的解决方案不起作用,因为它会导致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

重构/第二个解决方案

您可以创建一个 model 关注点,它将封装所有这些逻辑并使其更加动态,方法是允许您指定您想要行为的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

在您的 model 中:

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时,该模块很早就被调用

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

替代/最后解决方案:

如果你想要更动态和更健壮的东西,你仍然可以创建一个 model 关注点,但是你可以像这样在 model 的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 都会附加到该数组)

显然,处理这种情况的正确方法是将现有附件添加到上传表单中,如下所示:

  <% if @item.files.attached? %>
    <% @item.files.each do |file, i| %>
      <%= form.hidden_field :files, multiple: true, value: file.signed_id %>
    <% end %>
  <% end %>

这将导致 Active Storage 将新附件与现有附件合并,而不是用新附件替换现有附件。

我个人认为在添加新附件时默认清除所有现有附件是荒谬的,而删除允许将行为设置为绝大多数用户期望的配置选项更是荒谬。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM