在实例上重新定义单个方法以调用超类方法

时间:2012-03-25 15:03:45

标签: ruby-on-rails ruby activerecord

我们有两个Rails模型:PersonAdministrator。我们不允许在模型级别删除Administrator

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

我希望能够在测试期间解决这个问题,但仅限于在安装和拆卸过程中创建的特定实例。我不想改变班级的行为,因此class_evalremove_method是不可行的。

我尝试重新定义实际的实例#destroy方法:

def me.destroy
  super
end

或在单身类上重新定义它:

class << me
  def destroy
    super
  end
end

但那些仍然提出异常。我无法弄清楚如何让它隐式调用超类方法。我最终创建了自己的destroy!方法(因为它实际上并不是ActiveRecord中的方法),这违反了我不改变类行为的愿望:

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

有没有简单的方法来告诉单个实例方法调用它的超类方法?


最终答案:根据Holger Just链接的文章,我能够简单地明确调用超类方法:

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

2 个答案:

答案 0 :(得分:3)

我会尝试重构这种行为,使其更加适合测试。例如。你可以允许一个可选参数来破坏,例如i_know_what_im_doing必须设置为true才能执行销毁。或者,你可cancel destroy before_destroy挂钩

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

在测试中,您可以调用Administrator.skip_callback :before_destroy来忽略它并进行适当的破坏。

最后,您可以在测试中覆盖/存根方法。虽然您说您不想修改类的行为,但您仍然需要这样做(并且今天使用destroy!方法隐式执行此操作)。

答案 1 :(得分:1)

我不熟悉Ruby元编程,所以如果你可以在一个实例上调用超类的方法而不修改它,我就不会回答。但是您可以使用alias创建一个超类方法的挂钩:

class Administrator < Person

  alias :force_destroy :destroy 

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

这样,admin.destroy会引发异常,但admin.force_destroy实际上会调用ActiveRecord。