Ruby микширует и вызывает суперметоды
-
08-06-2019 - |
Вопрос
Итак, я провел рефакторинг своего кода в моем маленьком приложении Rails, пытаясь устранить дублирование и в целом облегчить себе жизнь (поскольку я люблю легкую жизнь).Частью этого рефакторинга было перемещение кода, общего для двух моих моделей, в модуль, который я могу включить туда, где мне это нужно.
Пока все идет так хорошо.Похоже, это сработает, но я только что столкнулся с проблемой, которую не уверен, как обойти.Модуль (который я назвал sendable) - это просто код, который обрабатывает отправку факсов, электронных писем или печать PDF-документа.Итак, например, у меня есть заказ на поставку, и у меня есть Внутренние заказы на продажу (образно сокращенные до ISO).
Проблема, с которой я столкнулся, заключается в том, что я хочу, чтобы некоторые переменные были инициализированы (инициализированы для людей, которые неправильно пишут: P) после загрузки объекта, поэтому я использовал after_initialize после инициализации крюк.Никаких проблем...пока я не начну добавлять еще несколько миксинов.
Проблема, с которой я сталкиваюсь, заключается в том, что у меня может быть after_initialize
в любом из моих миксинов, поэтому мне нужно включить супер позвоните в начале, чтобы убедиться, что другой миксер 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 if defined?(super)
Вот такой пример:
class A
end
class B < A
def t
super if defined?(super)
puts "Hi from B"
end
end
B.new.t
Другие советы
Вы пробовали alias_method_chain
?В принципе, вы можете сковать цепью все свои after_initialize
звонки.Он действует как декоратор:каждый новый метод добавляет новый уровень функциональности и передает управление "переопределенному" методу, чтобы сделать все остальное.
Включающий класс (вещь, которая наследуется от ActiveRecord::Base
, который в данном случае является Iso
) мог бы определять свои собственные after_initialize
, так что любое решение , отличное от alias_method_chain
(или другое сглаживание, которое сохраняет оригинал) может привести к перезаписи кода.Решение Ориона Эдвардса - лучшее, что я могу придумать.Есть и другие, но они такие далеко более халтурный.
alias_method_chain
также имеет преимущество создания именованных версий метода after_initialize, что означает, что вы можете настроить порядок вызовов в тех редких случаях, когда это имеет значение.В противном случае вы находитесь во власти любого порядка, в котором класс including включает миксины.
позже:
Я отправил вопрос в список рассылки ruby-on-rails-core о создании пустых реализаций по умолчанию для всех обратных вызовов.Процесс сохранения все равно проверяет их все, так что я не понимаю, почему их там не должно быть.Единственным недостатком является создание дополнительных пустых фреймов стека, но это довольно дешево в любой известной реализации.
Вы можете просто ввести туда краткое условие:
super if respond_to?('super')
и с вами все должно быть в порядке - никаких добавлений бесполезных методов;красиво и чисто.
Вместо того чтобы проверять, существует ли суперметод, вы можете просто определить его
class ActiveRecord::Base
def after_initialize
end
end
Это работает в моем тестировании и не должно нарушать какой-либо из ваших существующих кодов, потому что все ваши другие классы, которые его определяют, все равно будут просто молча переопределять этот метод