2016-01-13 20 views
6

devo moduli e le seguenti classi:Controllare se un metodo di classe è stato chiamato

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 

E 'possibile verificare, se il metodo configure dal modulo è stato chiamato? Ciò significa che A dovrebbe andare bene, ma B dovrebbe generare un errore, perché non ha mai chiamato configure.

Ho provato con il callback self.included, ma il metodo configure viene chiamato in seguito.

+0

Quando deve essere generata un'eccezione? Quando il processo esce? Come fai a sapere che non sarà chiamato in futuro? – ndn

+0

L'eccezione dovrebbe essere generata quando la classe B è stata definita. Ma hai un punto con * Come fai a sapere che non verrà chiamato in futuro? * – 23tux

+0

Una classe può essere riaperta.Un modulo può essere incluso dopo che una classe è stata definita. – ndn

risposta

0

Tecnicamente, @ndn ha ragione, potrebbe essere chiamato dopo che la classe è stata valutata. Tuttavia, sembra che quello che vuoi è di convalidare che il metodo configure sia stato chiamato ad un certo punto all'interno della definizione del corpo della classe (questo permetterà anche a tutti i moduli che sono stati inclusi, di finire di valutare, quindi se un modulo include chiamate configura metodo, va tutto bene).

La soluzione più vicina che è venuta in mente per affrontare questa situazione può essere trovato qui:

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

Il codice di cui sopra è un'implementazione metodi astratta per rubino, che tecnicamente non è quello che stai chiedendo (stai parlando di chiamare il metodo, i metodi astratti riguardano il controllo che una sottoclasse lo ha definito), ma lo stesso trucco che ho usato potrebbe essere applicato.

Fondamentalmente, sto usando la libreria dei punti di traccia di Ruby per controllare che la definizione della classe colpisca, a quel punto spara un evento, controllo se il metodo è stato definito e lancia un errore se non lo è. Quindi, finché chiami configure da WITHIN nelle tue classi, una soluzione simile potrebbe funzionare per te. Qualcosa di simile (non testato):

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 

Oppure, si potrebbe provare a utilizzare il modulo InheritanceHooks dalla mia biblioteca e saltare la movimentazione tracepoint manuale:

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

nota, anche se sto usando questo modello in produzione al momento, e tutto funziona alla grande con la risonanza magnetica, perché tracepoint è una libreria creata per il debug, ci sono alcune limitazioni quando si utilizza jruby. (in questo momento si interrompe se non si passa il flag di debug di jruby) - Ho aperto un problema per un po 'di tempo cercando di ottenere il tracepoint aggiunto senza dover abilitare il debug in modo esplicito.

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

0

Ecco un esempio basato sulla vostra struttura. Verifica l'istanziazione se è stato chiamato configure e funzionerà automaticamente con qualsiasi classe su cui è stato anteposto MyModule.

Controlla ogni istanza se è stato chiamato configure, ma sta semplicemente controllando un valore booleano in modo che non debba avere alcun impatto sulle prestazioni.

Ho cercato un modo per non definire un metodo anteposto per una classe specifica ma non ho trovato nulla.

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>' 
Problemi correlati