2012-03-25 6 views
2

У нас есть две модели Rails: Person и Administrator. Мы запрещая удаление 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_eval и remove_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 

Есть ли простой способ сказать одно метод экземпляра, чтобы вызвать его метод суперкласса?


Final Ответ: На основании статьи Holger Просто связаны, я был в состоянии просто вызвать метод суперкласса явным образом:

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

ответ

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!).

+0

Спасибо, Хольгер. Ваши предложения отличные, но мне в основном любопытно, почему мои вызовы 'super' не смогли вызвать метод суперкласса. У вас есть идеи, почему это происходит? – Brandan

+0

С помощью вашей конструкции вы создали метод overridden destroy в классе singleton (также называемом eigenclass) вашего экземпляра 'me'.Если вы находитесь в рамках этого метода, супер цепочка выглядит следующим образом: eigenclass -> class -> modules, включенные в класс -> родительский класс ... Это означает, что ваш 'супер' вызов вашего метода' destroy' на 'me' вызывает метод' destroy' в классе 'Aministrator'. Более подробную информацию о концепции eigenclass см. В [этой замечательной статье] (http://blog.madebydna.com/all/code/2011/06/24/eigenclasses-demystified.html). –

+0

Отлично! Эта статья прояснила это для меня. – Brandan

1

Я не знаком с Руби metaprograming, так что я не отвечу, если вы можете вызвать метод суперкласса в экземпляре без его модификации. Но вы можете создать крюк суперкласса методом с alias:

class Administrator < Person 

    alias :force_destroy :destroy 

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

с этим admin.destroy сгенерирует исключение, но admin.force_destroy фактически называют ActiveRecord уничтожить.

Смежные вопросы