Rails Paperclip como excluir o anexo?

84

Estou usando Paperclip (w / Amazon s3) no Rails 3. Eu quero excluir um anexo existente sem substituí-lo usando uma ação de atualização.

Eu encontrei apenas um exemplo disso aqui e não consegui fazer com que funcionasse, simplesmente não excluía e não havia nada nos logs para dizer o porquê. Eu queria fazer algo assim no formulário:

<%- unless @page.new_record? || [email protected]? -%>
    <%= f.check_box :image_delete, :label => 'Delete Image' %>
<%- end -%>

(página é o nome do modelo, imagem é o nome do atributo que contém o anexo)

Mas como faço para detectar essa caixa de seleção e, mais importante, como faço para excluir a imagem? Agradeço qualquer ajuda!

jyoseph
fonte

Respostas:

104

Primeiro, quando você cria um check_box em um form_for (que parece que você está), então o formulário deve, por padrão, enviar: image_delete como "1" se marcado e "0" se desmarcado. A declaração do método se parece com isto:

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

O que mostra que você pode atribuir outros valores se quiser, mas isso é opcional.

Em segundo lugar, a chamada para excluir manualmente um anexo sem excluir a instância do modelo à qual ele está anexado é:

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

E para realizar sua maneira de excluir as imagens por meio de uma caixa de seleção, talvez adicione algo assim ao seu modelo de página:

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

Dessa forma, ao criar seu formulário e adicionar a caixa de seleção: image_delete, ele carregará o valor padrão "0" da instância do usuário. E se esse campo estiver marcado, o controlador atualizará o image_delete para "1" e quando o usuário for salvo, ele verificará se a imagem deve ser excluída.

DanneManne
fonte
Neste exemplo, a imagem da Página # se refere a outro modelo que has_attached_file, ou a Página tem o anexo, imagem chamada?
John Bachir
@page é a variável de modelo que has_attached_file: imagem, mas parece que nomeei o modelo de usuário por algum motivo. Vou mudar e atualizar para esclarecer.
DanneManne
Ok, isso faz mais sentido :)
John Bachir
Não entendo por que você não faz apenas self.image.destroy ali - o clear remove o arquivo subjacente, mas mantém as meta informações sobre a imagem no modelo de página? Por que você quer fazer isso? (e não parece que seja isso que o questionador quer fazer)
John Bachir
11
Essa abordagem também funcionou para mim ... mas eu enfrentei um problema ... se o usuário marcar a caixa de seleção image_delete e também adicionar uma nova imagem ao mesmo tempo no formulário, a imagem antiga é excluída e a nova não é salva . Resolvi isso alterando a condição para self.image.clear if @image_delete == "1" and !image.dirty?no destroy_image?método
Zeeshan
97

has_attached_file :asset

=>

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

Não há necessidade de destruir o ativo, o Paperclip fará isso.

Na forma form.check_box(:delete_asset)será suficiente.

Benoit B.
fonte
3
Isso funciona e é mais simples do que a resposta @DanneManne IMHO. Muito bom! :)
MetalElf0
Como você escreveria uma especificação para isso?
Hengjie
1
Obrigado ! Para me ajudar a reduzir ainda mais: has_attached_file :asset has_destroyable_file :asset criei um inicializador para adicionar a config/initializers/ gist.github.com/3954054
ensolarado,
2
Eu encontrei um problema com este método por meio de aceita_nested_attributes pelo menos. before_validation não é acionado em um salvamento aninhado se nenhum outro atributo foi alterado. Veja minha resposta abaixo para a solução
Paul Odeon
4
@SurgePedroza Eu acredito que você precisa permitir o parâmetro: delete_asset, veja guias.rubyonrails.org/…
sman591
12

Esta é a resposta de Benoit, mas embrulhada em um módulo e cobrindo o caso extremo dos modelos de atributos aninhados, onde a caixa de seleção de destruição é a única coisa alterada no modelo.

Ele se aplicará a todos os acessórios do modelo.

# 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
fonte
1
Não sei por que isso não chamou mais atenção. attachment_definitionsacabei de salvar minha vida.
ok56k
Porém, esta linha também é necessária:attr_accessible :"delete_#{name}"
ok56k de
2
O exemplo acima deve estar em sua pasta de preocupações ou modelo. No modelo onde você deseja, basta adicionar a linha include DeletableAttachmentabaixo de quaisquer has_attached_filedeclarações
Paul Odeon
2
Em rails3 você precisará de attr_accessible: "delete _ # {name}" também
Mateu
1
Lembre- :delete_<your_attribute>se de permitir se você estiver usando parâmetros fortes em seu controlador
ivanxuu
5

lembre-se de adicionar isso ao seu modelo de página também:

attr_accessible :image_delete
Glenn McW
fonte
1

Versão modificada da solução de Paul, para suportar atributos customizados do Rails 5. Eu só queria que houvesse uma maneira de incluir o módulo no topo do arquivo, antes das has_attached_filedefinições.

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
fonte
0

Consegui fazer isso com menos código, apenas implementando um delete_attachmentno lado do modelo:

class MyModel < ApplicationRecord
  has_attached_file :image

  def image_delete=(other)
    self.image = nil if other == "1" or other == true
  end
end
Stwienert
fonte