smactive
smactive

Reputation: 58

Override rails 4 delete_all

I have written a concern to include into data models that will manage our auditable fields in our tables. This successfully overrides delete, destroy & update, but I am not able to catch the delete_all that executes an undesirable DELETE * FROM ....

module Concerns
  module Auditable
    extend ActiveSupport::Concern

    def auth_id
      Authentication.current ? Authentication.current.id : nil
    end

    def deletion_attributes
      { deleted_at: Time.now.utc, deleted_by: auth_id,
        modified_at: Time.now.utc, modified_by: auth_id }
    end

    def touch
      self.created_at ||= Time.now.utc
      self.created_by ||= auth_id
      self.modified_at = Time.now.utc
      self.modified_by = auth_id
    end

    def deleted?
      deleted_at.present?
    end

    def delete
      destroy
    end

    def destroy
      # destroy_associates (still to implement)
      update_attributes(deletion_attributes)
    end

    def undelete
      update_attributes(deleted_at: nil, deleted_by: nil,
                        modified_at: Time.now.utc, modified_by: auth_id)
    end

    def delete_all
      puts "this is never reached"
    end

    # Set ActiveRecord::Base of caller
    def self.included(base)
      unless base.ancestors.include?(ActiveRecord::Base)
        fail "You can only include this if #{base} extends ActiveRecord::Base"
      end
      base.class_eval do
        default_scope { where("#{table_name}.deleted_at IS NULL") }
        before_save :touch

        def self.show_deleted
          unscoped.where("#{table_name}.deleted_at IS NOT NULL")
        end

        def delete_all
          puts "this is also never reached"
        end
      end
    end
  end
end

Upvotes: 1

Views: 817

Answers (2)

Oss
Oss

Reputation: 4322

I believe this is not the right way to override the class methods.

def self.included(base)
  unless base.ancestors.include?(ActiveRecord::Base)
    fail "You can only include this if #{base} extends ActiveRecord::Base"
  end
  base.class_eval do
    default_scope { where("#{table_name}.deleted_at IS NULL") }
    before_save :touch

    def self.show_deleted
      unscoped.where("#{table_name}.deleted_at IS NOT NULL")
    end

    def delete_all
      puts "this is also never reached"
    end
  end
end

It works for me this way

  def self.included(base)
    base.extend(ClassMethods)
  end

  module ClassMethods
    def delete_all
      # do nothing
    end
  end

and make sure to prepend Concerns::Auditable to override the class methods.

class User < ActiveRecord::Base
  prepend Concerns::Auditable
end

You can also use the gem ActsAsParanoid. It does the job for you!

Upvotes: 0

Jesse Whitham
Jesse Whitham

Reputation: 824

What about destroy_all? Callbacks are not called when using delete_all from the docs it states "Deletes the records matching conditions without instantiating the records first, and hence not calling the destroy method nor invoking callbacks. This is a single SQL DELETE statement that goes straight to the database, much more efficient than destroy_all."

Upvotes: 1

Related Questions