Reputation: 14983
We have two Rails models: Person
and Administrator
. We're disallowing removal of Administrator
s 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
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
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