2016-01-13 3 views
6

У меня есть следующий модуль и классы:Проверьте, если метод класса был назван

module MyModule 
    def self.included base 
    base.extend(ClassMethods) 
    end 

    module ClassMethods 
    attr_reader :config 

    # this method MUST be called by every class which includes MyModule 
    def configure &block 
     @config = {} 
     block.call(@config) if block 
    end 
    end 
end 

class A 
    include MyModule 

    configure do |config| 
    # do sth with the config 
    end 
end 

class B 
    include MyModule 
end 

Можно ли проверить, если configure метод из модуля был вызван? Это означает, что A должно быть хорошо, но B должен выбросить ошибку, потому что он никогда не вызывал configure.

Я пробовал его в обратном вызове self.included, но метод configure вызывается впоследствии.

+0

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

+0

Исключение должно быть выбрано, когда был определен класс B. Но у вас есть точка с * Как вы можете знать, что в будущем это не будет называться? * – 23tux

+0

Класс может быть вновь открыт.Модуль может быть включен после определения класса. – ndn

ответ

0

Технически, @ndn является правильным, его можно вызвать после оценки класса. Однако похоже, что вы хотите проверить, что метод configure был вызван в какой-то момент в определении тела класса (это также позволит включить любые модули, чтобы завершить оценку, поэтому, если в модуль включены вызовы configure метод, это тоже хорошо).

Ближайшим решение, которое я придумал, чтобы решить эту ситуацию, можно найти здесь:

https://github.com/jasonayre/trax_core/blob/master/lib/trax/core/abstract_methods.rb

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

В принципе, я использую библиотеку точек трассировки ruby, чтобы посмотреть, как будет удаляться конец определения класса, после чего он запускает событие, я проверяю, был ли этот метод определен, и выдайте ошибку, если нет. Поэтому, пока вы вызываете configure из WITHIN ваших классов, подобное решение может сработать для вас. Нечто подобное (не проверено):

module MustConfigure 
    extend ::ActiveSupport::Concern 

    module ClassMethods 
    def inherited(subklass) 
     super(subklass) 
     subklass.class_attribute :_configured_was_called 
     subklass._configured_was_called = false 

     trace = ::TracePoint.new(:end) do |tracepoint| 
     if tracepoint.self == subklass #modules also trace end we only care about the class end 
      trace.disable 

      raise NotImplementedError.new("Must call configure") unless subklass._configured_was_called 
     end 
     end 

     trace.enable 

     subklass 
    end 

    def configure(&block) 
     self._configured_was_called = true 
     #do your thing 
    end 
    end 
end 

class A 
    include MustConfigure 
end 

class B < A 
    configure do 
    #dowhatever 
    end 
end 

class C < B 
    #will blow up here 
end 

Или, вы можете попробовать использовать модуль InheritanceHooks из моей библиотеки и пропустить ручная обработка Точки трассировки:

class BaseClass 
    include::Trax::Core::InheritanceHooks 
    after_inherited do 
    raise NotImplementedError unless self._configure_was_called 
    end 
end 

Обратите внимания, хотя я использую этот шаблон в производстве на данный момент, и все отлично работает на MRI, поскольку tracepoint - это библиотека, созданная для отладки, при использовании jruby существуют некоторые ограничения. (прямо сейчас он ломается, если вы не передаете флаг отладки jruby). Я снова открыл проблему, пытаясь получить добавленную точку трассировки без необходимости явно отлаживать отладку.

https://github.com/jruby/jruby/issues/3096

0

Вот пример, основанный на вашей структуре. Он проверяет при создании экземпляра, если configure был вызван, и будет работать автоматически с любым классом, на котором вы добавили MyModule.

Он проверяет при каждом экземпляре, если configure был вызван, но он просто проверяет логическое значение, поэтому он не должен иметь никакого влияния на производительность.

Я искал способ определения заранее заданного метода для определенного класса, но ничего не нашел.

module MyModule 
    def self.prepended base 
    base.extend(ClassMethods) 
    end 

    module ClassMethods 
    attr_reader :config 

    def configured? 
     @configured 
    end 

    def configure &block 
     @configured = true 
     @config = {} 
     block.call(@config) if block 
    end 
    end 

    def initialize(*p) 
    klass = self.class 
    if klass.configured? then 
     super 
    else 
     raise "Please run #{klass}.configure before calling #{klass}.new" 
    end 
    end 
end 

class A 
    prepend MyModule 

    configure do |config| 
    config[:a] = true 
    puts "A has been configured with #{config}" 
    end 
end 

class B 
    prepend MyModule 
end 

A.new 
puts "A has been instantiated" 

puts 

B.new 
puts "B has been instantiated" 

# => 
# A has been configured with {:a=>true} 
# A has been instantiated 

# check_module_class.rb:27:in `initialize': Please run B.configure before calling B.new (RuntimeError) 
# from check_module_class.rb:50:in `new' 
# from check_module_class.rb:50:in `<main>' 
Смежные вопросы