【问题标题】:Rails Active Storage - Keep Existing Files / Uploads?Rails Active Storage - 保留现有文件/上传?
【发布时间】:2022-11-11 08:39:27
【问题描述】:

我有一个 Rails 模型:

has_many_attached :files

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

我有一个控制器黑客,由于许多原因,这不太理想:

What is the correct way to update images with has_many_attached in Rails 6

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

【问题讨论】:

    标签: ruby-on-rails rails-activestorage


    【解决方案1】:

    看起来有一个configuration 正是这样做的

    config.active_storage.replace_on_assign_to_many = false
    

    不幸的是,根据当前的 Rails 源代码,它已被弃用,它将是 removed in Rails 7.1

    config.active_storage.replace_on_assign_to_many 已弃用,将在 Rails 7.1 中删除。在升级之前,确保您的代码在将config.active_storage.replace_on_assign_to_many 设置为true 的情况下运行良好。 要将新的附件附加到活动存储关联,最好使用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 %>
    

    它可能还需要模型中的读者,并且可能需要一个更好的名称,但它应该展示这个概念。

    【讨论】:

    • 这是一个很好的答案!我实际上遇到了控制器解决方案,但新方法的想法要好得多。 ?
    • 如果我在控制器中执行此操作,那么我所有的新文件都将被添加两次。我猜是.attach 方法和model.update 方法之一。我们是否还需要制作params[:model][:files] = nil 来防止这种情况?
    • 当然你不能同时通过。如果您想明确使用attach,您必须避免将params[:model][:files] 传递给model.update。例如,您可以通过从允许的参数中删除:files 或使用不同的属性来做到这一点。如果将params[:model][:files] 设置为nil,则必须小心,并在调用attach 方法之后和将属性传递给model.update 之前执行此操作。
    • 不确定在我这边工作,它会导致“堆栈级别太深”
    【解决方案2】:

    上面建议的用于覆盖作者的解决方案不工作.

    第一个解决方案

    基于ActiveStorage源代码at this line

    您可以像这样覆盖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
    

    重构/第二个解决方案

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

    注意:prependinclude 模块无关紧要,因为 ActiveStorage 生成的方法被添加到此 generated module 中,当您从 ActiveRecord::Base 继承时很早就调用 here

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

    替代/最后解决方案:

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-02-01
      • 1970-01-01
      • 2019-11-19
      • 1970-01-01
      • 2020-11-04
      • 1970-01-01
      • 2021-03-01
      • 1970-01-01
      相关资源
      最近更新 更多