ruby alias_method in class method

1.4k Views Asked by At

It seems that alias_method doesn't work the same way inside a class << self block as it does outside. Specifically, when I use alias_method for an instance method and subsequently override the method, the alias name can be used to access the original method. However, I am not able to do this with a class method.

This is my use case: I am overriding the destroy and destroy_all methods that are inherited from ActiveRecord base because the code as currently written makes it very easy to accidentally delete rows when you meant to delete join records. That design could change with a larger project, but for now this is my solution as for the most part, no one will ever want to be deleting rows from this table. But in order to still allow intentional deletion of rows in the table for the well-intentioned, I am using alias_method in order to preserve a handle to the original destroy and destroy_all methods. Using the alias is working to access the original :destroy (instance) method, but is not allowing me to access the original :desotry_all (class) method

class HoldReason < ActiveRecord::Base
  class << self
    alias_method :remove_hold_reasons_from_table, :destroy_all

    def destroy_all
      raise "Nope"
    end
  end

  alias_method :remove_hold_reason, :destroy

  def destroy
    raise "Nope"
  end
end

Here we see that while this strategy does work for the instance method to allow successful deletion of a single row:

> HoldReason.find_by(title: "Management Review").remove_hold_reason
  HoldReason Load (0.7ms)  SELECT  `hold_reasons`.* FROM `hold_reasons` WHERE `hold_reasons`.`title` = 'Management Review' LIMIT 1
   (0.5ms)  BEGIN
  SQL (4.6ms)  DELETE FROM `hold_reasons` WHERE `hold_reasons`.`id` = 23
   (0.6ms)  COMMIT
=> #<HoldReason id: 23, title: "Management Review", category: "Operations">

I am not able to access the original :destroy_all method to delete multiple rows in one query; instead, I get the overridden method even when I use the alias:

> HoldReason.remove_hold_reasons_from_table
  HoldReason Load (0.7ms)  SELECT `hold_reasons`.* FROM `hold_reasons`
RuntimeError: Nope

What is going on that I can't seem to do this for the class methods, and how can I fix it (other than just use a raw SQL query to do the delete)?

1

There are 1 best solutions below

0
On

The answer is that I was not getting the new :destroy_all method; I was correctly getting the old :destroy_all method, and this called :destroy under the hood. Of course, by calling :destroy, it got the new :destroy method, which raised the error. So it only seemed like I was getting the new class method because the new class method and the new instance method raise exactly the same error.

This is made clearer if I modify this slightly:

class HoldReason < ActiveRecord::Base
  class << self
    alias_method :remove_hold_reasons_from_table, :destroy_all

    def destroy_all
      raise "Don't destroy_all (multiple: class method)"
    end
  end

  alias_method :remove_hold_reason, :destroy

  def destroy
    raise "Don't destroy (single: instance method)"
  end

end

results in

> HoldReason.remove_hold_reasons_from_table
RuntimeError: Don't destroy (single: instance method)