Ruby mixins и вызов супер методов

Хорошо, так что я рефакторинг мой код в моем маленьком приложении Rails в попытке удалить дублирование, и в целом сделать мою жизнь проще (как мне нравится легкая жизнь). Частью этого рефакторинга было перемещение кода, общего для двух моих моделей, в модуль, который я могу включить там, где он мне нужен.

Пока все хорошо. Похоже, что это сработает, но я только что столкнулся с проблемой, которую я не уверен, как обойти. Модуль (который я назвал sendable), просто будет кодом, который обрабатывает факс, электронную почту или печать PDF документа. Так, например, у меня есть заказ на покупку, и у меня есть внутренние заказы на продажу (образно сокращенные до ISO).

Проблема, которую я поразил, заключается в том, что я хочу, чтобы некоторые переменные были инициализированы (инициализированы для людей, которые не пишут правильно 😛 ) после загрузки объекта, поэтому я использовал крюк after_initialize. Не проблема… пока не начну добавлять новые миксины.

Проблема у меня есть, в том, что я могу иметь after_initializeв любой из моих mixins, поэтому мне нужно включить супер звонок в начале, чтобы убедиться, что другие mixin after_initializeзвонки получить вызов. Это здорово, пока я в конечном итоге не позвоню супер, и я получу ошибку, потому что нет супер позвонить.

Вот небольшой пример, в случае, если я не был достаточно запутанным:

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

Итак, если у каждого из mixins есть вызов after_initialize с супер-вызовом, как я могу остановить последний супер-вызов от возникновения ошибки? Как я могу проверить, что супер метод существует, прежде чем я вызову его?

5 ответов

  1. Вместо того, чтобы проверять, существует ли супер метод, вы можете просто определить его

    class ActiveRecord::Base
        def after_initialize
        end
    end
    

    Это работает в моем тестировании, и не должно нарушать любой из вашего существующего кода, потому что все ваши другие классы, которые определяют его, будут просто молча переопределять этот метод в любом случае

  2. Вы пробовали alias_method_chain? Вы можете в основном прикованы все ваши after_initializeзвонки. Он действует как декоратор: каждый новый метод добавляет новый уровень функциональности и передает управление на «переопределенный» метод, чтобы сделать остальное.

  3. Класс including (вещь , которая наследуется отActiveRecord::Base, которая в данном случае является Iso) может определить свой собственный after_initialize, поэтому любое решение, отличное от alias_method_chain(или другого псевдонима, который сохраняет исходный), рискует перезаписать код. @Orion Edwards-лучшее решение, которое я могу придумать. Есть и другие, но они гораздо более банальны.

    alias_method_chain также имеет преимущество создания именованных версий метода after_initialize, что означает, что вы можете настроить порядок вызова в тех редких случаях, когда это имеет значение. В противном случае вы находитесь во власти любого порядка, в котором класс including включает миксины.

    позже:

    Я отправил вопрос в список рассылки ruby-on-rails-core о создании пустых реализаций по умолчанию всех обратных вызовов. Процесс сохранения проверяет их все в любом случае, поэтому я не вижу, почему они не должны быть там. Единственным недостатком является создание дополнительных пустых кадров стека, но это довольно дешево для каждой известной реализации.

  4. Вы можете просто бросить быстрый условный там:

    super if respond_to?('super')
    

    и вы должны быть в порядке-без добавления бесполезных методов; хороший и чистый.

  5. Вы можете использовать это:

    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