Brandan
Brandan

Reputation: 14983

Redefine a single method on an instance to call superclass method

We have two Rails models: Person and Administrator. We're disallowing removal of Administrators at the model level:

class Person < ActiveRecord::Base
end

class Administrator < Person
  def destroy
    raise "Can't remove administrators."
  end
end

me = Administrator.new
me.destroy              # raises an exception

I'd like to be able to get around this during testing, but only for specific instances created during setup and teardown. I don't want to change the behavior of the class, so class_eval and remove_method aren't feasible.

I tried to redefine the actual instance's #destroy method:

def me.destroy
  super
end

or redefine it on the singleton class:

class << me
  def destroy
    super
  end
end

but those still raised the exception. I couldn't figure out how to get it to call the superclass method implicitly. I ended up creating my own destroy! method (since that's not actually a method in ActiveRecord), which sort of violates my desire not to change the behavior of the class:

def destroy!
  ActiveRecord::Persistence.instance_method(:destroy).bind(self).call
end

Is there any simple way to tell a single instance method to call its superclass method?


Final Answer: Based on the article Holger Just linked to, I was able to simply call the superclass method explicitly:

def me.destroy
  self.class.superclass.instance_method(:destroy).bind(self).call
end

Upvotes: 3

Views: 1622

Answers (2)

Holger Just
Holger Just

Reputation: 55833

I'd try to refactor the behavior to be more test-friendly. E.g. you could allow an optional parameter to destroy e.g. i_know_what_im_doing that has to be set to true to carry out the destroy. Alternatively you could cancel the destroy with a before_destroy hook like

class Administrator < Person
  def before_destroy(record)
    # You can't destroy me
    false
  end
end

In your tests, you can then call Administrator.skip_callback :before_destroy to ignore it and to have a proper destroy.

Finally, you could overwrite / stub the method in your tests. While you say you don't want to modify the class's behavior, you still have to do that (and implicitly do that with your destroy! method today).

Upvotes: 3

Baldrick
Baldrick

Reputation: 24340

I'm not familiar with Ruby metaprograming, so I wont answer if you can a call a method of the super class on an instance without modifying it. But you can create a hook to a superclass method with alias :

class Administrator < Person

  alias :force_destroy :destroy 

  def destroy
    raise "Can't remove administrators."
  end
end

With this, admin.destroy will raise an exception, but admin.force_destroy will actually call the ActiveRecord destroy.

Upvotes: 1

Related Questions