module ActsAsParanoid::Core
def self.included(base)
def self.included(base) base.extend ClassMethods end
def decrement_counters_on_associations
def decrement_counters_on_associations update_counters_on_associations :decrement_counter end
def delete
def delete self.class.delete(id) if persisted? stale_paranoid_value freeze end
def deleted?
def deleted? return true if @destroyed if self.class.string_type_with_deleted_value? paranoid_value == paranoid_configuration[:deleted_value] elsif self.class.boolean_type_not_nullable? paranoid_value == true else !paranoid_value.nil? end end
def deleted_fully?
def deleted_fully? @destroyed end
def destroy
def destroy if !deleted? with_transaction_returning_status do run_callbacks :destroy do if persisted? # Handle composite keys, otherwise we would just use # `self.class.primary_key.to_sym => self.id`. self.class .delete_all([Array(self.class.primary_key), Array(id)].transpose.to_h) decrement_counters_on_associations end @_trigger_destroy_callback = true stale_paranoid_value self end end elsif paranoid_configuration[:double_tap_destroys_fully] destroy_fully! end end
def destroy!
def destroy! destroy || raise( ActiveRecord::RecordNotDestroyed.new("Failed to destroy the record", self) ) end
def destroy_dependent_associations!
def destroy_dependent_associations! self.class.dependent_associations.each do |reflection| assoc = association(reflection.name) next unless (klass = assoc.klass).paranoid? klass .only_deleted.merge(get_association_scope(assoc)) .each(&:destroy!) end end
def destroy_fully!
def destroy_fully! with_transaction_returning_status do run_callbacks :destroy do destroy_dependent_associations! if persisted? # Handle composite keys, otherwise we would just use # `self.class.primary_key.to_sym => self.id`. self.class .delete_all!([Array(self.class.primary_key), Array(id)].transpose.to_h) decrement_counters_on_associations end @destroyed = true freeze end end end
def each_counter_cached_association_reflection
def each_counter_cached_association_reflection _reflections.each do |_name, reflection| yield reflection if reflection.belongs_to? && reflection.counter_cache_column end end
def get_association_scope(dependent_association)
def get_association_scope(dependent_association) ActiveRecord::Associations::AssociationScope.scope(dependent_association) end
def increment_counters_on_associations
def increment_counters_on_associations update_counters_on_associations :increment_counter end
def paranoid_value
def paranoid_value send(self.class.paranoid_column) end
def paranoid_value=(value)
def paranoid_value=(value) write_attribute(self.class.paranoid_column, value) end
def persisted?
def persisted? !(new_record? || @destroyed) end
def recover(options = {})
def recover(options = {}) return if !deleted? options = { recursive: self.class.paranoid_configuration[:recover_dependent_associations], recovery_window: self.class.paranoid_configuration[:dependent_recovery_window], raise_error: false }.merge(options) self.class.transaction do run_callbacks :recover do increment_counters_on_associations deleted_value = paranoid_value self.paranoid_value = self.class.recovery_value result = if options[:raise_error] save! else save end recover_dependent_associations(deleted_value, options) if options[:recursive] result end end end
def recover!(options = {})
def recover!(options = {}) options[:raise_error] = true recover(options) end
def recover_dependent_association(reflection, deleted_value, options)
def recover_dependent_association(reflection, deleted_value, options) assoc = association(reflection.name) return unless (klass = assoc.klass).paranoid? if reflection.belongs_to? && attributes[reflection.association_foreign_key].nil? return end scope = klass.only_deleted.merge(get_association_scope(assoc)) # We can only recover by window if both parent and dependant have a # paranoid column type of :time. if self.class.paranoid_column_type == :time && klass.paranoid_column_type == :time scope = scope.deleted_inside_time_window(deleted_value, options[:recovery_window]) end recovered = false scope.each do |object| object.recover(options) recovered = true end assoc.reload if recovered && reflection.has_one? && assoc.loaded? end
def recover_dependent_associations(deleted_value, options)
def recover_dependent_associations(deleted_value, options) self.class.dependent_associations.each do |reflection| recover_dependent_association(reflection, deleted_value, options) end end
def stale_paranoid_value
def stale_paranoid_value self.paranoid_value = self.class.delete_now_value clear_attribute_changes([self.class.paranoid_column]) end
def update_counters_on_associations(method_sym)
def update_counters_on_associations(method_sym) each_counter_cached_association_reflection do |assoc_reflection| reflection_options = assoc_reflection.options next unless reflection_options[:counter_cache] associated_object = send(assoc_reflection.name) next unless associated_object counter_cache_column = assoc_reflection.counter_cache_column associated_object.class.send(method_sym, counter_cache_column, associated_object.id) associated_object.touch if reflection_options[:touch] end end