module Cms
  module Behaviors
    module SoftDeleting
      def self.included(model_class)
        model_class.extend(MacroMethods)
      end

      module MacroMethods
        def uses_soft_delete?
          !!@uses_soft_delete
        end

        def handle_missing_table_error_during_startup(message, e)
          Rails.logger.debug "#{message}: #{e.inspect}"
        end

        def uses_soft_delete(options={})
          @uses_soft_delete = true

          scope :not_deleted, -> {where (["#{table_name}.deleted = ?", false])}
          class << self
            alias_method :delete_all!, :delete_all
          end

          extend ClassMethods
          include InstanceMethods
         #attr_accessible :deleted

          # By default, all queries for blocks should filter out deleted rows.
          begin
            default_scope {where(:deleted => false)}
          # This may fail during gem loading, if no DB or the table does not exist. Log it and move on.
          rescue StandardError => e
            handle_missing_table_error_during_startup("Can't set a default_scope for soft_deleting", e)
          end
        end
      end

      # TODO: Refactor this class to remove need for overriding count, delete_all, etc.
      # Should not be necessary due to introduction of 'default_scope'.
      #
      # 2. TODO: Allow a record to define its own default_scope that doesn't 'override' this one.
      # See http://github.com/fernandoluizao/acts_as_active for an implementation of this
      module ClassMethods
        # Returns a content block even if it is marked as deleted.
        # @param [Hash] options Hash suitable to be passed to '#where'
        def find_with_deleted(options)
          if options.class==Fixnum
            self.unscoped.where(:id=>options).first
          else
            self.unscoped.where(options).first
          end
        end

        # Returns a count of all records of this type, including those marked as deleted.
        #
        # Behaves like ActiveRecord.count is originally implemented.
        #
        # @param args Same params as ActiveRecord.count
        def count_with_deleted(* args)
          self.unscoped.count(* args)
        end

        def delete_all(conditions=nil)
          where(conditions).update_all(["deleted = ?", true])
        end

        def exists?(id_or_conditions)
          query = if id_or_conditions.is_a?(Hash) || id_or_conditions.is_a?(Array)
            where id_or_conditions
          else
            where(:id => id_or_conditions)
          end
          query.count > 0
        end
      end
      module InstanceMethods

        # Destroying a soft deletable model should mark the record as deleted, and not actually remove it from the database.
        #
        # Overrides original destroy method
        def destroy
          run_callbacks :destroy do
            if self.class.publishable?
              update_attributes(:deleted => true, :publish_on_save => true)
            else
              update_attributes(:deleted => true)
            end
          end
        end

        def mark_as_deleted!
          destroy
        end

        def destroy!
          transaction { super.destroy if super!=true }
        end

        def destroyed?
          @destroyed
        end
      end
    end
  end
end
