How to copy one object from one model to another model with Rails ActiveStorage

Viewed 2387

I'm currently using Rails 5.2.2. I'm trying to copy one image between 2 models. So I want 2 files, and 2 entries in blobs and attachments. My original object/image is on AWS S3.

My original model is photo, my target model is image.

I tried this:

image.file.attach(io: open(best_photo.full_url), filename: best_photo.filename, content_type: best_photo.content_type)

full_url is a method added in photo.rb:

include Rails.application.routes.url_helpers
def full_url
  rails_blob_path(self.file, disposition: "attachment", only_path: true)
end

I got this error, as if the file was not found:

No such file or directory @ rb_sysopen - /rails/active_storage/blobs/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBHZz09IiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--2f865041b01d2f2c323a20879a855f25f231289d/881dc909-88ab-43b6-8148-5adbf888b399.jpg?disposition=attachment

I tried other different things such (this method is used when displaying images with image_tag() and works correctly:

def download_variant(version)
    variant = file_variant(version)
    return rails_representation_url(variant, only_path: true, disposition: "attachment")
  end

Same error.

I verified and the file is present on the S3 server. What did I miss ?

Thanks.

4 Answers

You can attach the source blob to the target:

image.file.attach best_photo.file.blob

OK, I got it. I used service_url:

image.file.attach(io: open(best_photo.file_variant("large").service_url), filename: best_photo.file.blob.filename, content_type: best_photo.file.blob.content_type)

Updated for Rails 6+ - this worked for me

image.file.attach(io: StringIO.new(best_photo.download),
                  filename: best_photo.filename,
                  content_type: best_photo.content_type)

It's possible to use file_blob instead of file.blob

You can copy using update

image.update(file: best_photo.file_blob)

or attach

image.file.attach(best_photo.file_blob)

Both methods actually just create new blob association, there is no physical copying of attachment. So be careful when you call image.blob.purge or if you have dependent: :purge_later

If you want real copy you need to read attachment with ActiveStorage::Blob#download

image.file.attach(
  io: StringIO.new(best_photo.file.download),
  filename: best_photo.file.filename,
  content_type: best_photo.file.content_type
)

or with ActiveStorage::Blob#open

best_photo.file_blob.open do |tempfile|
  image.file.attach(
    io: tempfile,
    filename: best_photo.file.filename,
    content_type: best_photo.file.content_type
  )
end

Be careful with large files

Related