86

I am using Paperclip (w/ Amazon s3) on Rails 3. I want to delete an existing attachment without replacing it using an update action.

I've only found one example of this here and could not get that to work, it just wouldn't delete and there was nothing in the logs to say why. I wanted to do something like this on the form:

<%- unless @page.new_record? || !@page.image? -%>
    <%= f.check_box :image_delete, :label => 'Delete Image' %>
<%- end -%>

(page is the name of the model, image is the attribute name which holds the attachment)

But how do I detect that checkbox and more importantly, how do I delete the image? I appreciate any help!

jyoseph
  • 5,435
  • 9
  • 45
  • 64

6 Answers6

107

First off, when you create a check_box in a form_for (which it looks like you are), then the form should by default send :image_delete as "1" if checked and "0" if unchecked. The method declaration looks like this:

def check_box(method, options = {}, checked_value = "1", unchecked_value = "0")

Which shows that you can assign other values if you want to, but that is of course optional.

Secondly, the call to manually delete an attachment without deleting the model instance to which it is attached to is:

@page.image.destroy #Will remove the attachment and save the model
@page.image.clear #Will queue the attachment to be deleted

And to accomplish your way of deleting the images through a checkbox, perhaps add something like this to your Page model:

class Page < ActiveRecord::Base
  has_attached_file :image

  before_save :destroy_image?

  def image_delete
    @image_delete ||= "0"
  end

  def image_delete=(value)
    @image_delete = value
  end

private
  def destroy_image?
    self.image.clear if @image_delete == "1"
  end
end

This way, when you create your form and add the :image_delete checkbox, it will load the default value "0" from the User instance. And if that field is checked then the controller will update the image_delete to "1" and when the User is saved, it will check if the image is to be deleted.

cweston
  • 11,297
  • 19
  • 82
  • 107
DanneManne
  • 21,107
  • 5
  • 57
  • 58
  • In this example, does Page#image refer to another model which `has_attached_file`, or does Page have the attachment, named image? – John Bachir Jan 05 '11 at 20:04
  • @page is the model variable that has_attached_file :image, but I seem to have named the model User for some reason. I'll change and update to clarify. – DanneManne Jan 06 '11 at 02:02
  • Okay, that makes more sense :) – John Bachir Jan 07 '11 at 20:13
  • I don't understand why you don't just do self.image.destroy there -- does clear remove the underlying file, but maintain the meta information about image in the Page model? Why would you want to do this? (and it doesn't seem like that's what the question asker wants to do) – John Bachir Jan 07 '11 at 20:16
  • 11
    This approach also worked for me... but I faced one issue... if the user checks image_delete checkbox and also add a new image at the same time in the form, then the old image is deleted and the new image is not saved. I solved this by changing the condition to `self.image.clear if @image_delete == "1" and !image.dirty?` in `destroy_image?` method – Zeeshan Aug 15 '11 at 12:43
  • As I experienced `@page.image.destroy` only delete attachment in the cloud. So you specifically need to save your model record `@page.save`. Otherwise your record will be considered still having an attached image/file but the generated link would point to an error. (Rails 4.1 / Paperclip 4.2.0 / Amazon S3) – Sylvain FARNAULT Mar 02 '22 at 16:24
98

has_attached_file :asset

=>

    attr_accessor :delete_asset
    before_validation { asset.clear if delete_asset == '1' }

No need to destroy asset, Paperclip will do it.

In the form form.check_box(:delete_asset) will suffice.

Benoit B.
  • 3,561
  • 24
  • 19
  • 3
    This works and it's simpler than the @DanneManne answer IMHO. Very good! :) – MetalElf0 Mar 14 '12 at 10:23
  • How would you write a spec for this? – Hengjie Aug 19 '12 at 11:10
  • 1
    Thanks ! To help me slim this down even more to : `has_attached_file :asset` `has_destroyable_file :asset` I created an initializer to add to `config/initializers/` https://gist.github.com/3954054 – Sunny Oct 25 '12 at 17:51
  • 2
    I found a problem with this method through accepts_nested_attributes at least. before_validation doesn't get triggered on a nested save if no other attributes have been altered. See my answer below for the solution – Paul Odeon May 28 '14 at 09:58
  • I keep getting this error in Rails 4. Unpermitted parameters: delete_asset – Serge Pedroza Jan 03 '15 at 19:56
  • 4
    @SurgePedroza I believe you need to permit the parameter :delete_asset, see http://guides.rubyonrails.org/action_controller_overview.html#strong-parameters – sman591 Jan 05 '15 at 01:05
12

This is Benoit's answer, but wrapped in a module, and covering the edge case of nested attribute models where the destroy tickbox is the only thing changed on the model.

It will apply to all attachments on the model.

# This needs to be included after all has_attached_file statements in a class
module DeletableAttachment
  extend ActiveSupport::Concern

  included do
    attachment_definitions.keys.each do |name|

      attr_accessor :"delete_#{name}"

      before_validation { send(name).clear if send("delete_#{name}") == '1' }

      define_method :"delete_#{name}=" do |value|
        instance_variable_set :"@delete_#{name}", value
        send("#{name}_file_name_will_change!")
      end

    end
  end

end
Paul Odeon
  • 4,407
  • 1
  • 37
  • 37
  • 1
    Don't know why this hasn't gotten more attention. `attachment_definitions` just saved my life. – t56k Jun 10 '14 at 01:04
  • Needs this line too though: `attr_accessible :"delete_#{name}"` – t56k Jun 10 '14 at 02:30
  • So do I put this in the same file as the model? Where in it exactly? – Heisenberg Sep 05 '14 at 22:30
  • 2
    The example above should be in your concerns or model folder. In the model where you want it just add the line `include DeletableAttachment` below any `has_attached_file` statements – Paul Odeon Sep 08 '14 at 12:36
  • 2
    In rails3 you will need attr_accessible :"delete_#{name}" also – Mateu Nov 04 '14 at 14:43
  • @PaulOdeon What does the `define method` in your code do an why do I need a file name in the last line? If I'm deleting an image, isn't that happening in the `before validation` line? – Moosa Feb 11 '15 at 06:33
  • @emm could you respond to my comment above? I pasted the module code below my has_attached block in the model. then added a checkbox in form. not sure what i'm missing. – Moosa Feb 11 '15 at 18:09
  • name is the name of of the file you have defined i.e. "photo" you will therefore need to have `f.checkbox :delete_photo` define-method dynamically creates the function that will mark the file for deletion – Paul Odeon Feb 12 '15 at 10:09
  • 1
    Remember to permit `:delete_` if you are using strong parameters in your controller – ivanxuu May 02 '15 at 10:56
5

remember to add this to your Page model too:

attr_accessible :image_delete
Glenn McW
  • 90
  • 1
  • 1
1

Modified version of Paul's solution, to support Rails 5 custom attributes. I just wish there were a way to include the module at the top of the file, before has_attached_file definitions.

module Mixins
  module PaperclipRemover

    extend ActiveSupport::Concern

    included do
      attachment_definitions.keys.each do |name|

        attribute :"remove_#{name}", :boolean

        before_validation do
          self.send("#{name}=", nil) if send("remove_#{name}?")
        end

      end
    end

  end

end
JBlake
  • 1,004
  • 12
  • 29
0

Was able to achieve this with less code, by just implementing a delete_attachment on the model's side.:

class MyModel < ApplicationRecord
  has_attached_file :image

  def image_delete=(other)
    self.image = nil if other == "1" or other == true
  end
end
stwienert
  • 3,402
  • 24
  • 28