Итак, я занимаюсь рефакторингом кода в своем небольшом приложении Rails, пытаясь устранить дублирование и в целом облегчить себе жизнь (поскольку я люблю легкую жизнь). Частью этого рефакторинга было перемещение кода, который является общим для двух моих моделей, в модуль, который я могу включать туда, куда мне нужно.
Пока все хорошо. Кажется, что все работает, но я столкнулся с проблемой, которую не знаю, как решить. Модуль (который я назвал sendable), будет просто кодом, который обрабатывает отправку факса, электронной почты или печать PDF документа. Так, например, у меня есть заказ на поставку, и есть внутренние заказы на продажу (образно сокращенно ISO).
Проблема, с которой я столкнулся, заключается в том, что я хочу, чтобы некоторые переменные инициализировались (инициализировались для тех, кто не знает правильного написания :P ) после загрузки объекта, поэтому я использовал after_initialize хук. Никаких проблем... пока я не начал добавлять новые миксины.
Проблема в том, что я могу иметь after_initialize
в любом из моих миксинов, поэтому мне нужно включить вызов super в начале, чтобы убедиться, что вызовы других миксинов after_initialize
будут вызваны. Это замечательно, пока я не вызову super и не получу ошибку, потому что нет никакого super для вызова.
Вот небольшой пример, если я не достаточно запутал вас:
class Iso < ActiveRecord::Base
include Shared::TracksSerialNumberExtension
include Shared::OrderLines
extend Shared::Filtered
include Sendable::Model
validates_presence_of :customer
validates_associated :lines
owned_by :customer
order_lines :despatched # Mixin
tracks_serial_numbers :items # Mixin
sendable :customer # Mixin
attr_accessor :address
def initialize( params = nil )
super
self.created_at ||= Time.now.to_date
end
end
Итак, если каждый из миксинов имеет вызов after_initialize с вызовом super, как я могу остановить этот последний вызов super от возникновения ошибки? Как я могу проверить, что суперметод существует до того, как я его вызову?
Пробовали ли вы использовать alias_method_chain
? По сути, вы можете объединить в цепочку все вызовы после_инициализации
. Он действует как декоратор: каждый новый метод добавляет новый уровень функциональности и передает управление "переопределенному" методу, который делает все остальное.
Включенный класс (то, что наследуется от ActiveRecord::Base
, который в данном случае является Iso
) может определить свой собственный after_initialize
, поэтому любое решение, кроме alias_method_chain
(или другого алиасинга, сохраняющего оригинал), рискует переписать код. Решение @Orion Edwards' - лучшее, что я смог придумать. Есть и другие, но они далее более хакерские.
Преимуществом alias_method_chain
является также создание именованных версий метода after_initialize, что означает, что вы можете настроить порядок вызова в тех редких случаях, когда это имеет значение. В противном случае, вы находитесь во власти того порядка, в котором включающий класс включает миксины.
позже:
Я опубликовал вопрос в списке рассылки ruby-on-rails-core о создании пустых реализаций всех обратных вызовов по умолчанию. Процесс сохранения все равно проверяет их наличие, так что я не вижу причин, почему они не должны быть там. Единственный недостаток - создание дополнительных пустых стековых фреймов, но это довольно дешево для каждой известной реализации.
Вы можете просто бросить быстрое условное предложение там:
super if respond_to?('super')
и Вы должны быть в порядке - никакие добавляющие бесполезные методы; хороший и чистый.
Вместо того чтобы проверять, существует ли суперметод, вы можете просто определить его
class ActiveRecord::Base
def after_initialize
end
end
Это работает в моем тестировании, и не должно сломать ни один из ваших существующих кодов, потому что все ваши другие классы, которые определяют его, будут просто молча переопределять этот метод в любом случае.