21

I need to copy a file from one carrier wave object to another. They are different tables and different types of uploaders.

I started with:

user.avatar = image.content

(where user and image are model instances, avatar and content are the carrierwave mounted uploaders) which worked sometimes. It seems to work all the time locally, with a file storage, but intermittent when using fog and s3.

In a mailing list post I found this code:

user.avatar = image.content.file

that again worked sometimes.

My working solution so far is:

require "open-uri"

begin
  user.avatar = open(image.url)
rescue Errno::ENOENT => e
  begin
    user.avatar = open(image.path)
  rescue Errno::ENOENT => e
    # Ok, whatever.
  end
end

which is not only ugly, but fails to pass the extension validation because the opening of a remote file doesn't maintain the extension (jpg, png, etc.).

Pablo Fernandez
  • 279,434
  • 135
  • 377
  • 622

6 Answers6

15

Perhaps one way you can do it is to set a remote image URL as per the Carrierwave gem documentation?

user.remote_avatar_url = image.url
rebagliatte
  • 2,110
  • 1
  • 20
  • 25
Dia Kharrat
  • 5,948
  • 3
  • 32
  • 43
  • Do you image image.content.url? I'm not sure this would work, as avatar has a lot of versions with different processes that need a local file to work, and I don't want both records pointing to the same file, I want a copy of the file. – Pablo Fernandez Mar 30 '12 at 09:44
  • 1
    The way remote_blah_url works is that it essentially "downloads" the file from that URL and reprocesses it. See http://stackoverflow.com/questions/5007575/how-to-assign-a-remote-file-to-carrierwave for instance. – Ibrahim Nov 19 '12 at 07:52
  • 5
    Not work if the image is not served over HTTP. You will get the follow error: `Icon trying to download a file which is not served over HTTP` – Pioz Jul 24 '13 at 21:32
  • @Pioz were you able to find a way to do this? – Mohamad Oct 22 '13 at 15:57
  • 3
    `user.avatar = object.image` seems the better / proper way since `remove_avatar_url` is only for `remote urls` – C404 May 11 '15 at 13:48
9

From solutions discussed here I created simple CopyCarrierwaveFile gem to do this

usage is something like this:

original_resource = User.last
new_resource      = User.new

CopyCarrierwaveFile::CopyFileService.new(original_resource, new_resource, :avatar).set_file    

new_resource.save
nev_resource.avatar.url # https://...image.jpg
equivalent8
  • 13,754
  • 8
  • 81
  • 109
1

Here's a (albeit hacky) solution to that doesn't require an HTTP request to fetch the image:

module UploadCopier
  def self.copy(old, new)
    new.instance_variable_set('@_mounters', nil)

    old.class.uploaders.each do |column, uploader|
      new.send("#{column}=", old.send(column))
    end
  end
end

old_user = User.last
new_user = User.new
UploadCopier.copy(old_user, new_user)
new_user.save
Daniel Upton
  • 5,561
  • 8
  • 41
  • 64
1

I needed to copy a reference from one model to another model and I was successfully able to do so by doing the following:

my_new_model.update_column('attachment', my_other_model.attributes["attachment"]);

In this scenario, I did not care to actually make a copy of the file, nor did I care that 2 records were now linked to the same file (my system never deletes or modifies files after uploaded).

This may be useful to anyone who wants to just copy the reference to a file from one model to another model using the same uploader.

PressingOnAlways
  • 11,948
  • 6
  • 32
  • 59
  • Didn't work for me. https://stackoverflow.com/questions/45901396/carrierwave-sharing-an-image-between-two-models – Dercni Aug 27 '17 at 03:21
  • 1
    @Dercni makes a good note - this method only works if no other fields except the `attachment` field is used to determine where the file is located. – PressingOnAlways Aug 27 '17 at 19:27
  • Well, after hours of struggling with remote url I have decided to use your solution and have a common path for both models and simply share the file, as you have shown. – Dercni Aug 28 '17 at 06:28
  • Will there be any issue if system ever deletes such references, other than broken link in other model/column? – Zia Ul Rehman Mughal Aug 21 '21 at 12:59
1

You can do this by copying files.

store_path is a carrierwave method from Uploader class. It returns uploaded file's folder relative path.

this clone file method should be called after model record is saved. If record not saved, store_path may return wrong path if you specify store_dir with model id in uploader.

def clone_carrierwave_file(column_name)
  origin_files = Dir[File.join(Rails.root, 'public', original_record.send(column_name).store_path, '*')]
  return if origin_files.blank?
  new_file_folder = File.join(Rails.root, 'public', send(column_name).store_path)
  FileUtils.mkdir new_file_folder if !Dir.exist? new_file_folder
  FileUtils.cp(origin_files, new_file_folder)
end

Hope it works.

ianYa
  • 19
  • 4
0

I just wanted to copy an avatar reference from one object to another, and what worked for me was:

objectB.avatar.retrieve_from_store!(objectA.avatar.identifier)
objectB.save
Brett Donald
  • 6,745
  • 4
  • 23
  • 51