如何在Rails 5.2中复制存储在ActiveStorage中的文件?

37

我有一个使用ActiveStorage的模型:

class Package < ApplicationRecord
  has_one_attached :poster_image
end
我该如何创建一个 Package 对象的副本,并包含初始 poster_image 文件的副本。大致类似于以下内容:
original = Package.first
copy = original.dup
copy.poster_image.attach = original.poster_image.copy_of_file
6个回答

40

更新您的模型:

class Package < ApplicationRecord
  has_one_attached :poster_image
end

将源软件包的海报图像 Blob 附加到目标软件包:

source_package.dup.tap do |destination_package|
  destination_package.poster_image.attach(source_package.poster_image.blob)
end

2
我发现我还需要将 dependent 选项设置为 false,以避免在删除原始软件包时删除 blob: has_one_attached: poster_image, dependent: false - Robban
是的!抱歉我忘了。 - George Claghorn
4
除非我理解有误,否则这并不会创建文件的副本。它只是使两个记录指向同一个文件。此外,即使与该文件关联的所有记录都被销毁,它仍将保留在存储系统中。 - ndnenkov
3
你确定这个信息是最新的吗?我刚试了下,ActiveStorage::PurgeJob似乎只会执行SELECT 1 AS one FROM "active_storage_attachments" WHERE "active_storage_attachments"."blob_id" = 55 LIMIT 1,并且如果有其他附件引用该Blob时不会删除它。所以dependent: false应该是不必要的。 - ndnenkov
我指的是销毁之前的这个 - ndnenkov
Blob 上有一个 before_destroy 钩子:raise ActiveRecord::InvalidForeignKey if attachments.exists? - Mirko

21
如果您想要完整的文件副本,以便原始记录和克隆记录都有其自己的附加文件副本,请执行以下操作:
在Rails 5.2中,获取此代码并将其放入config/initializers/active_storage.rb,然后使用此代码进行复制:
ActiveStorage::Downloader.new(original.poster_image).download_blob_to_tempfile do |tempfile|
  copy.poster_image.attach({
    io: tempfile, 
    filename: original.poster_image.blob.filename, 
    content_type: original.poster_image.blob.content_type 
  })
end

在Rails 5.2之后(每当发布包含此提交时),您只需执行以下操作:

original.poster_image.blob.open do |tempfile|
  copy.poster_image.attach({
    io: tempfile, 
    filename: original.poster_image.blob.filename, 
    content_type: original.poster_image.blob.content_type 
  })
end

感谢你的原始答案和对Rails的贡献,George。:)


1
不幸的是,除了主分支之外,Rails 的第二部分仍未发布版本。 - stwienert
这个方法很有效,我更喜欢它而不是下面的解决方案,因为在运行所有测试时,直接附加图像而不先打开它最终会出现90%的故障。 - Rich Steinmetz
使用另一种解决方案时,我几乎每次运行都会遇到“ActiveStorage :: FileNotFoundError:ActiveStorage :: FileNotFoundError”错误。 - Rich Steinmetz
使用这个解决方案时,当整个过程被包装在ActiveRecord事务中时,我会遇到IOError的问题,因此它也有其缺点。 - Rich Steinmetz

11

通过查看Rails的测试,特别是在blob模型测试中找到了答案

所以对于这种情况

class Package < ApplicationRecord
  has_one_attached :poster_image
end

您可以像这样复制附件

original = Package.first
copy = original.dup
copy.poster_image.attach \
  :io           => StringIO.new(original.poster_image.download),
  :filename     => original.poster_image.filename,
  :content_type => original.poster_image.content_type

相同的方法也适用于has_many_attachments

class Post < ApplicationRecord
  has_many_attached :images
end

original = Post.first
copy = original.dup

original.images.each do |image|
  copy.images.attach \
    :io           => StringIO.new(image.download),
    :filename     => image.filename,
    :content_type => image.content_type
end

8

这对我有用:

copy.poster_image.attach(original.poster_image.blob)

3
为什么这不是最佳答案? :) - Honza

7
在Rails 5中,Jethro的回答很有效。在Rails 6中,我不得不进行修改,修改如下:
  image_io = source_record.image.download
  ct = source_record.image.content_type
  fn = source_record.image.filename.to_s
  ts = Time.now.to_i.to_s

  new_blob = ActiveStorage::Blob.create_and_upload!(
    io: StringIO.new(image_io),
    filename: ts + '_' + fn,
    content_type: ct,
  )

  new_record.image.attach(new_blob)

来源:


1
正常工作。谢谢! - Jul

6
< p > Benjamin的回答略有不同,但对我有用。

copy.poster_image.attach({
    io: StringIO.new(original.poster_image.blob.download), 
    filename: original.poster_image.blob.filename, 
    content_type: original.poster_image.blob.content_type 
  })


谢谢!这个带有文件名和内容类型的扩展版本对我很有用。 - pastullo
我更喜欢Benjamin的解决方案,因为在运行所有测试时,直接附加图像而不先打开它最终产生了90%的故障,几乎每次运行都会出现ActiveStorage:: FileNotFoundError:ActiveStorage::FileNotFoundError。 - Rich Steinmetz

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接