Carrierwave - destroy object only after it has been deleted from my storage

Asked

Viewed 195 times

4

Carrierwave only deletes the 'mounted' file after the object in the database has been removed:

after_commit :remove_avatar! :on => :destroy

https://github.com/carrierwaveuploader/carrierwave

I am implementing a Backgrounddestroyable Center and for that I have a worker who deletes the files.

The field deleted_at records when the object was marked for complete removal and to ensure that the objects are only actually removed by the worker I needed to reset the destroy at the conference.

If one of the Workers receives a timeout I lose the reference of my orphaned files in S3, since my objects in the database have been deleted.

What should I do to make sure I don’t end up with orphans in S3? Call remove_avatar! right before the object.destroy and then make a skip_callback?

It’s safe to do that?

  • Sorry, I don’t understand the part of Timeout, what gets Timeout? Your script or Amazon?

  • @Ricardo my script (in case my worker Rails) is the one who receives the timeout. For some reason he may not be able to communicate with S3 while proceeding with file deletions and hence orphans problem.

  • before_destroy :remove_avatar! ??

  • My problem is that I was doing a Concern for soft delete (Backgrounddestroyable) and the logic got a little more complex (I was resetting Destroy and removing the file in before_destroy would not work). Anyway I think it’s okay to call the before_destroy :remove_avatar! even if the carrierwave will call the method again after the commit.

2 answers

1


What I did was call remove_avatar! before calling super() and do the removal indeed, without changing anything in the callbacks.

For registration, follow the code of the Concern:

# BackgroundDestroyable classes MUST have deleted_at:datetime column
module BackgroundDestroyable
  extend ActiveSupport::Concern

  included do
    default_scope { where(deleted_at: nil) }
    scope :deleted, -> { unscoped.where.not(deleted_at: nil) }
  end

  def destroy(mode = :background)
    if mode == :background
      unless self.deleted_at.present?
        update_attribute :deleted_at, Time.now
        Resque.enqueue(BackgroundDestroyer, self.id, self.class.to_s)
      end
    elsif mode == :now
      self.class.uploaders.each do |uploader, uploader_class|
        self.send("remove_#{uploader}!")
      end
      super()
    end
  end

  def destroy_now!
    self.destroy(:now)
  end
end

0

For me, it makes more sense to call remove_avatar! before calling the object.destroy. Thus, you can always ensure that you will not have orphaned objects in S3, checking whether the object has actually been removed.

I’d rather not touch the callbacks, unless I know exactly what I’m doing. Also, I always prefer to leave the explicit behavior in the code and callbacks are not very explicit. And, from experience, you’ll be thanking yourself in a few months when you need to look at this script and do some maintenance on it.

  • But to make a remove_avatar! before Object.Destroy without using callback can be problematic, since my Destroy has some conditions within the model (for example, only delete an object when it has no child [more or less like Amazon does with her Buckets, which you can only delete if they are empty])

  • So I see as feasible, you separate all these pre-Destroy tests into another method, call this method, then call the remove_avatar! and finally the object.destroy. I see, in this way that each method is responsible only for a small responsibility, rather than having a great method that does everything and is difficult to maintain.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.