eraticus
eraticus

Reputation: 407

Carrierwave, creating a duplicate attachment when duplicating its containing model

I would like to duplicate a model. The original model contains an attachment through Carrierwave. Ideally, a new attachment would be created, that is a copy of the original image, for the new model object.

I have looked through the Carrierwave documentation, and googled this problem, but have not found a solution that creates a new duplicate of the original image. Is this reasonable? Possible?

Upvotes: 30

Views: 11964

Answers (7)

Salma Gomaa
Salma Gomaa

Reputation: 1112

This worked for me:

user = User.first
dup_user = user.dup
dup_user.photo = user.photo
dup_user.save

Reference: https://codeutility.org/ruby-on-rails-carrierwave-creating-a-duplicate-attachment-when-duplicating-its-containing-model-stack-overflow/

Upvotes: 0

victorpolko
victorpolko

Reputation: 379

I needed to fully duplicate the whole version set on S3, while some of the versions were cropped.

Unfortunately, remote_#{column}_url= method was of no help, because by the time the versions are recreated, there are no crop params on the model: I used RailsCasts approach using attr_accessor to crop the avatar, and those params weren't stored in the DB.

After some research and a lot of failures, I found this answer and noticed that copy_to method. It turned out that both SanitizedFile and Storage::Fog have it, so it's possible to use it for local and S3 files. I didn't however investigate how it literally works and decided to let Carrierwave a chance to take care of it.

class AvatarUploader 
  …

  def duplicate_to(target)
    return unless file.present? && target.logo.file.present?

    versions.keys.each do |version|
      public_send(version).file.copy_to(target.avatar.public_send(version).path)
    end
  end
end

That's all it takes to fully duplicate the images, no matter if they are cropped or not. There's a catch, however: you should only call duplicate_to after the model is already saved with other avatar, or the target path would be nil. Thus, one useless round of processing takes place for the new record.

new_user.assign_attributes(old_user.slice(:avatar, :avatar_alignment))

# Won't work!
old_user.avatar.duplicate_to(new_user) # => as the `new_user` hasn't persisted yet, its avatar files are Tempfiles
new_user.save # => will recreate the versions from the original image, losing the cropped versions!

# But this works
new_user.save # => the avatar will be stored as a set of versions created from the original (useless processing)
old_user.avatar.duplicate_to(new_user) # => the avatar files will be rewritten by the copies of old_user files

I think it's a good idea to store the crop params somewhere in the DB in a JSON-like object for such cases (and to be protected from losing cropping data when you have to recreate_versions!), but if that's not an option, this solution might be what you seek.

As this thread is the first G-link when searching for carrierwave duplicate, I decided to post this answer exactly here. Carrierwave 1.3.2 with fog-aws 1.2.0.

Hope this helps someone or the future me!

Upvotes: 0

MegaTux
MegaTux

Reputation: 1651

Extracted from the Carrierwave wiki page:

YourModel.find_each do |ym| 
  begin
    ym.process_your_uploader_upload = true # only if you use carrierwave_backgrounder
    ym.your_uploader.cache_stored_file! 
    ym.your_uploader.retrieve_from_cache!(ym.your_uploader.cache_name) 
    ym.your_uploader.recreate_versions!(:version1, :version2) 
    ym.save! 
  rescue => e
    puts  "ERROR: YourModel: #{ym.id} -> #{e.to_s}"
  end
end

Upvotes: 1

Koen.
Koen.

Reputation: 26989

For me with CarrierWave 0.10 this works just fine:

user = User.first
dup_user = user.dup
dup_user.photo = user.photo
dup_user.save

Although I'm not sure how this works out when using cloud storage like S3

Upvotes: 3

Martin M
Martin M

Reputation: 8638

While copy_carrierwave_file is a neat gem it is not nescessary as long as you use local storage.
carrierwave can use local files as source of attachments and you can use this to duplicate the attachment:

first_user = User.first
duplicate_user = first_user.dup
duplicate_user.photo = File.open(first_user.photo.file.file) if first_user.photo.present?
duplicate_user.save

This is more efficient than routing the image twice through your web server.

Upvotes: 8

equivalent8
equivalent8

Reputation: 14237

Try this gem https://github.com/equivalent/copy_carrierwave_file , it handles both local storage and Fog storage

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

Upvotes: 7

derekyau
derekyau

Reputation: 2946

I don't believe Carrierwave has this option. However, you can make use of the *_remote_url= method to set the new model's picture to be a duplicate of the first.

Here's a brief example

Say I have a model which has_one :photo attached with carrierwave. I can duplicate, the model, set the photo to the previous one and save it. Example:

first_model = User.first
duplicate_model = first_model.dup #(where the dup code duplicates everything else you need)
duplicate_model.remote_photo_url = first_model.photo_url
duplicate_model.save

This would then "copy" the photo from the first object into your second as a new carrierwave attachment.

Upvotes: 31

Related Questions