Carrierwave: Deleting files outside of forms

Updated . Posted . Visible to the public. Repeats.

TL;DR Use user.update!(remove_avatar: true) to delete attachments outside of forms. This will have the same behavior as if you were in a form.


As you know, Carrierwave file attachments work by mounting an Uploader class to an attribute of the model. Though the database field holds the file name as string, calling the attribute will always return the uploader, no matter if a file is attached or not. (Side note: use #present? on the uploader to check if the file exists.)

class User < ApplicationRecord
  mount :avatar, AvatarUploader
end

Now there are several ways to delete an attached file. The invocations do not have the same effect (while each is correct in its domain):

  1. Setting remove_avatar to true and saving:
    user.remove_avatar = true
    user.save!
    # or even shorter
    user.update!(remove_avatar: true)
    
    This lives in the ActiveRecord domain. It will delete the file AND empty the database field.
    Advantages:
    • The behavior is the same as in a usual form. First the DB field gets cleared and the transaction commits, then the file gets deleted in a callback.
  2. Using the dynamic #remove_avatar! Show archive.org snapshot method Carrierwave patches onto the model
    user.remove_avatar!
    user.save!
    
    The method #remove_avatar! on the model lives in the ActiveRecord domain. It will delete the file AND empty the database field. Subsequent calls to avatar.present? will return false.
    Note that Carrierwave itself will NOT touch the database, so you need to save the model after removing its attachment in order to persist its removal (the file is gone nonetheless, but without saving the model would forget).
    Also note that an already instantiated record will never know that its file is gone if you delete the file from another instantiation. Example:
    u = User.last
    u.avatar.present? # => true
    User.find_each do |u|
      u.remove_avatar!
      u.save!
    end
    u.avatar.present? # => true
    u.reload.avatar.present? # => true
    User.find(u.id).avatar.present? # => false
    

    Note

    #remove_avatar! itself does not save. Carrierwave internally calls this method as and after_commit callback, so it saves to the DB first and removes the file afterwards. If you call this method manually and then save, you risk that your change to the DB will fail after you already deleted the file.

  3. Using the #remove! Show archive.org snapshot method from the uploader:
    user.avatar.remove!
    
    NOTE: #remove! on the uploader lives in the "file storage" domain. It will delete the file, but leave the database field untouched, so the uploader will believe it is still there. Subsequent calls to avatar.present? will return true.

Which one should you use?

Unless you deal with low-level logic and have good reasons to do otherwise, probably stick with option 1.


Reference Show archive.org snapshot (2011)

Dominik Schöler
Last edit
Klaus Weidinger
License
Source code in this card is licensed under the MIT License.
Posted by Dominik Schöler to makandra dev (2017-12-22 08:40)