2014-04-11 8 views
9

Mantenere questo semplice. Diciamo che ho un modello User e un modello Post:Come nascondere i record, piuttosto che eliminarli (eliminazione graduale da zero)

class User < ActiveRecord::Base 
    # id:integer name:string deleted:boolean 

    has_many :posts  
end 

class Post < ActiveRecord::Base 
    # id:integer user_id:integer content:string deleted:boolean 

    belongs_to :user 
end 

Ora, supponiamo che un amministratore vuole "eliminare" (nascondere) un post. Quindi, in pratica, tramite il sistema, imposta l'attributo deleted di post a 1. Come dovrei ora visualizzare questo post nella vista? Devo creare un attributo virtuale sul post come questo:

class Post < ActiveRecord::Base 
    # id:integer user_id:integer content:string deleted:boolean 

    belongs_to :user 

    def administrated_content 
     if !self.deleted 
      self.content 
     else 
      "This post has been removed" 
     end 
    end 
end 

Mentre che avrebbe funzionato, voglio implementare quanto sopra in un gran numero di modelli, e non posso fare a meno che Copy + incollando il sopra comparativo in tutti i miei modelli potrebbe essere DRYer. Molto più asciutto

Penso anche che inserire una colonna deleted in ogni singolo modello cancellabile nella mia app sia un po 'macchinoso. Sento che dovrei avere un tavolo 'stato'. Quali sono i vostri pensieri su questo:

class State 
    #id:integer #deleted:boolean #deleted_by:integer 

    belongs_to :user 
    belongs_to :post 
end 

e poi interrogare self.state.deleted nel comparatore? Questo richiederebbe un tavolo polimorfico? Ho provato solo una volta polimorfico e non ho potuto farlo funzionare. (era su un modello autoreferenziale piuttosto complesso, mente). E questo ancora non affronta il problema di avere un metodo di classe molto, molto simile nei miei modelli per verificare se un'istanza viene eliminata o meno prima di visualizzare il contenuto.

Nell'attributo deleted_by, sto pensando di posizionare l'id dell'amministratore che l'ha eliminato. Ma che dire quando un amministratore ripristina un post? Forse dovrei avere solo un id edited_by.

Come impostare una relazione di tipo dependent: :destroy tra l'utente e i suoi messaggi? Perché ora voglio fare questo: dependent: :set_deleted_to_0 e non sono sicuro di come farlo.

Inoltre, non vogliamo semplicemente impostare gli attributi cancellati del post su 1, perché in realtà vogliamo cambiare il messaggio che emette il nostro administrated_content. Ora vogliamo che dica, This post has been removed because of its user has been deleted. Sono sicuro di poter entrare e fare qualcosa di hacky, ma voglio farlo correttamente sin dall'inizio.

Cerco anche di evitare gemme quando posso perché sento che mi sto perdendo l'apprendimento.

+0

Di solito uso un campo 'deleted_at' che è un campo datetime. Quando questo campo è completato, significa che questo record è cancellato (si comporta come un booleano ma fornisce una data) – MrYoshiji

+2

Dai un'occhiata a paranoia gem -> https://github.com/radar/paranoia – usha

risposta

13

io di solito uso un campo denominato deleted_at per questo caso:

class Post < ActiveRecord::Base 
    scope :not_deleted, lambda { where(deleted_at: nil) } 
    scope :deleted, lambda { where("#{self.table_name}.deleted_at IS NOT NULL") } 

    def destroy 
    self.update_attributes(deleted_at: DateTime.current) 
    end 

    def delete 
    destroy 
    end 

    def deleted? 
    self.deleted_at.present? 
    end 
    # ... 

vogliono condividere questo functionnality tra più modelli?

=> Crea un'estensione!

# lib/extensions/act_as_fake_deletable.rb 
module ActAsFakeDeletable 
    # override the model actions 
    def destroy 
    self.update_attributes(deleted_at: DateTime.current) 
    end 

    def delete 
    self.destroy 
    end 

    def undestroy # to "restore" the file 
    self.update_attributes(deleted_at: nil) 
    end 

    def undelete 
    self.undestroy 
    end 

    # define new scopes 
    def self.included(base) 
    base.class_eval do 
     scope :destroyed, where("#{self.table_name}.deleted_at IS NOT NULL") 
     scope :not_destroyed, where(deleted_at: nil) 
     scope :deleted, lambda { destroyed } 
     scope :not_deleted, lambda { not_destroyed } 
    end 
    end 
end 

class ActiveRecord::Base 
    def self.act_as_fake_deletable(options = {}) 
    alias_method :destroy!, :destroy 
    alias_method :delete!, :delete 
    include ActAsFakeDeletable 

    options = { field_to_hide: :content, message_to_show_instead: "This content has been deleted" }.merge!(options) 

    define_method options[:field_to_hide].to_sym do 
     return options[:message_to_show_instead] if self.deleted_at.present? 
     self.read_attribute options[:field_to_hide].to_sym 
    end 
    end 
end 

Usage:

class Post < ActiveRecord::Base 
    act_as_fake_deletable 

sovrascrivendo i valori di default:

class Book < ActiveRecord::Base 
    act_as_fake_deletable field_to_hide: :title, message_to_show_instead: "This book has been deleted man, sorry!" 

Boom! Fatto.

Attenzione: Questo modulo sovrascrivere destroy e delete metodi del ActiveRecord, il che significa che non sarà in grado di distruggere il vostro record utilizzando più questi metodi. Ad esempio, anziché sovrascrivere, è possibile creare un nuovo metodo, denominato soft_destroy. Quindi, nella tua app (o console), dovresti usare soft_destroy se pertinente e usare i metodi destroy/ quando vuoi davvero "distruggere" il record.

+4

Se OP ha bisogno di questo in un sacco di modelli come impliciti nel post, una preoccupazione sarebbe un buon posto per questo. –

+0

@MichaelKohl Vorrei un esempio di questo – Starkers

+0

Ho aggiornato la mia risposta con un modulo @Starkers per esempio – MrYoshiji

Problemi correlati