2011-06-16 9 views
8

Ho una coppia di moduli che estendono metodo mancante:Come comporre moduli contenenti method_missing in rubino

module SaysHello 
    def respond_to?(method) 
     super.respond_to?(method) || !!(method.to_s =~ /^hello/) 
    end 
    def method_missing(method, *args, &block) 
     if (method.to_s =~ /^hello/) 
      puts "Hello, #{method}" 
     else 
      super.method_missing(method, *args, &block) 
     end 
    end 
end 

module SaysGoodbye 
    def respond_to?(method) 
     super.respond_to?(method) || !!(method.to_s =~ /^goodbye/) 
    end 
    def method_missing(method, *args, &block) 
     if (method.to_s =~ /^goodbye/) 
      puts "Goodbye, #{method}" 
     else 
      super.method_missing(method, *args, &block) 
     end 
    end 
end 

class ObjectA 
    include SaysHello 
end 

class ObjectB 
    include SaysGoodbye 
end 

Questo tutto funziona bene, per esempio ObjectA.new.hello_there uscite "Hello, hello_there". Allo stesso modo, ObjectB.new.goodbye_xxx uscite "Goodbye, xxx". respond_to? funziona anche, ad es. ObjectA.new.respond_to? :hello_there return true.

Tuttavia, questo non funziona molto bene quando si desidera utilizzare entrambi i SaysHello e SaysGoodbye:

class ObjectC 
    include SaysHello 
    include SaysGoodbye 
end 

Mentre ObjectC.new.goodbye_aaa funziona correttamente, ObjectC.new.hello_a agisce strano:

> ObjectC.new.hello_aaa 
Hello, hello_aaa 
NoMethodError: private method `method_missing' called for nil:NilClass 
    from test.rb:22:in `method_missing' (line 22 was the super.method_missing line in the SaysGoodbye module) 

Produce correttamente, quindi genera un errore. Anche respond_to? non è corretto, ObjectC.new.respond_to? :hello_a restituisce false.

Infine, l'aggiunta di questa classe:

class ObjectD 
    include SaysHello 
    include SaysGoodbye 

    def respond_to?(method) 
     super.respond_to?(method) || !!(method.to_s =~ /^lol/) 
    end 


    def method_missing(method, *args, &block) 
     if (method.to_s =~ /^lol/) 
      puts "Haha, #{method}" 
     else 
      super.method_missing(method, *args, &block) 
     end 
    end 
end 

agisce anche in modo strano. ObjectD.new.lol_zzz funziona, tuttavia ObjectD.new.hello_a and ObjectD.new.goodbye_t rilascia entrambi un'eccezione di nome dopo aver emesso la stringa corretta. respond_to? ha esito negativo anche per i metodi ciao e addio.

C'è un modo per far funzionare tutto correttamente? Una spiegazione di come method_missing, Moduli e super interagiscono sarebbe anche molto utile.

MODIFICA: coreyward ha risolto il problema, se uso super invece di super.<method-name>(args...) in tutti i metodi che definisco, il programma funziona correttamente. Non capisco perché sia ​​così, quindi ho fatto un'altra domanda al riguardo a What does super.<method-name> do in ruby?

+0

moduli inclusi sono aggiunti alla catena di ereditarietà; non sostituiscono o sostituiscono i metodi. Quindi, se ogni metodo_missing chiama super, alla fine verranno tutti chiamati. Vedi la mia risposta qui sotto. – ChrisPhoenix

risposta

5

Quando si ridefinisce un metodo, si ridefinisce un metodo; periodo.

Quello che si sta facendo quando si include il secondo modulo con il metodo method_missing definisce sta ignorando lo method_missing definito in precedenza. Puoi mantenerlo aliasing prima di ridefinirlo, ma potresti volerlo fare attenzione.

Inoltre, non so perché si sta chiamando super.method_missing. Una volta che la tua definizione di method_missing è fuori dai giochi dovresti far sapere a Ruby che può continuare la catena alla ricerca di un modo per gestire la chiamata, tutto solo chiamando lo super (non è necessario passare argomenti o specificare un nome di metodo).

A proposito di Super (aggiornamento)

Quando si chiama super Rubino prosegue su per la catena di ereditarietà alla ricerca della prossima definizione del metodo invocato, e se ne trova uno che chiama e restituisce la risposta. Quando chiami il numero super.method_missing chiami il metodo method_missing sulla risposta a super().

Prendete questo esempio (un po 'sciocco):

class Sauce 
    def flavor 
    "Teriyaki" 
    end 
end 

# yes, noodles inherit from sauce: 
# warmth, texture, flavor, and more! ;) 
class Noodle < Sauce 
    def flavor 
    sauce_flavor = super 
    "Noodles with #{sauce_flavor} sauce" 
    end 
end 

dinner = Noodle.new 
puts dinner.flavor  #=> "Noodles with Teriyaki sauce" 

Si può vedere che è un metodo eccellente proprio come qualsiasi altro, si fa solo un po' di magia dietro le quinte. Se chiami super.class qui vedrai String, poiché "Teriyaki" è una stringa.

Ha senso ora?

+0

Ma l'importazione del secondo modulo non ha sovrascritto il metodo method_missing del primo, altrimenti ObjectC.new.hello_a non avrebbe emesso Hello, hello_a (più un NameError). L'utilizzo di super invece di super.method_missing (...) ha effettivamente risolto il problema, tutte le chiamate method_missing e response_to? le chiamate ora funzionano correttamente. Non capisco perché però. –

+0

@nanothief: Ho aggiornato la domanda per (si spera) spieghi di più su come 'super' funzioni in Ruby. Penso che chiarirò le cose per te. – coreyward

+0

grazie, questo era il problema che avevo con super. Supponevo che super.method stesse chiamando il metodo sulla classe genitore, non chiamando il metodo sul risultato di chiamare il metodo corrente sulla superclasse. –

1

http://www.perfectline.ee/blog/activerecord-method-missing-with-multiple-inheritance

Questo articolo spiega esattamente come funziona: Ogni nuovo modulo non sovrascrive o sostituire i metodi - invece, i suoi metodi sono inseriti nella catena di ereditarietà. Ecco perché chiamare super da ogni method_missing alla fine chiama tutte le method_missing.

La classe rimane la più bassa nella catena di ereditarietà e il modulo aggiunto per ultimo è adiacente alla classe.

Quindi:

class Foo 
    include A 
    include B 
end 

risultati in Kernel -> A -> B -> Foo