Active Storage 的限制意味着您肯定需要复制 blob。在 Active Storage 中,blob 键是唯一索引的,因此不同的文件名不能引用它两次。
如果您的存储服务允许您在原地有效地复制 blob,那么 Rails 端的启用方法是create_before_direct_upload!。尽管名称有些误导,但此方法被专门记录为提供一个新的(但未附加的)blob 记录,期望内容本身将上传到应用程序之外。然后在后续操作中附加 Blob 记录。
这就是直接上传的工作原理,我们可以挂钩到相同的两阶段机制;在这种特殊情况下,“上传”将改为特定于服务的复制。
原位复制的方式因存储服务而异,但例如:
因此,一个说明性的演示代码块可能是:
def blob_clone(current_blob, **options)
blob_parameters = {
filename: current_blob.filename,
byte_size: current_blob.byte_size,
checksum: current_blob.checksum,
content_type: current_blob.content_type,
metadata: current_blob.metadata,
**options
}
service = current_blob.service
new_blob = ActiveStorage::Blob.create_before_direct_upload!(**blob_parameters)
case service.class.name
when 'ActiveStorage::Service::S3Service'
bucket = service.bucket
current_object = bucket.object(current_blob.key)
current_object.copy_to(bucket.object(new_blob.key))
when 'ActiveStorage::Service::DiskService'
current_path = service.path_for(current_blob.key)
new_path = service.path_for(new_blob.key)
FileUtils.mkdir_p(File.dirname(new_path))
FileUtils.ln(current_path, new_path)
else
raise ArgumentError, "unknown blob storage service #{service.class.name}"
end
new_blob
end
这样使用:
current_blob = mymodel.contract.blob
new_blob = blob_clone(current_blob, filename: 'newfilename.pdf')
new_model.contract.attach(new_blob)
虽然我建议根据您的应用程序结构和偏好重构此示例。
代码注释
在上面的插图中,case service.class.name 相当不可爱,只是为了保持演示代码自包含而编写。问题在于 Rails 甚至没有为未使用的后端存储服务加载类常量。如果您没有跨环境使用不同的存储服务,那么 case 语句可能是不必要的,如果这被完全实现为一个 gem,或者重构为一个初始化程序,我可能会巧妙地将它作为猴子修补/改进服务类本身。