2012-12-07 13 views
7

Nel codice seguente, il metodo roar non è definito nella classe Lion, ma può ancora essere chiamato utilizzando method_missing.Quando utilizzare `method_missing`

class Lion 
    def method_missing(name, *args) 
    puts "Lion will #{name}: #{args[0]}" 
    end 
end 

lion = Lion.new 
lion.roar("ROAR!!!") # => Lion will roar: ROAR!!! 

In quali situazioni e come si usa questo method_missing? Ed è sicuro da usare?

+0

Metodo Ghost, di solito uno strumento di metaprogrammazione, immagino. Fai attenzione all'efficienza. Il metodo Ghost è lento. – halfelf

+0

Penso che tu intenda "classe Lion" piuttosto che "Class Lion". –

+0

@EricWalker: grazie, errore di battitura. – tokhi

risposta

8

Sommario: Quando si usa? Quando ti semplificherà la vita, e non complicheranno la vita degli altri.


Ecco un esempio che mi viene in mente. È dalla gemma redis_failover.

# Dispatches redis operations to master/slaves. 
def method_missing(method, *args, &block) 
    if redis_operation?(method) 
    dispatch(method, *args, &block) 
    else 
    super 
    end 
end 

Qui controlliamo se il metodo chiamato è in realtà un comando di connessione redis. Se è così, lo deleghiamo alle connessioni sottostanti. In caso contrario, inoltrare a super.

Un altro esempio famoso di applicazione method_missing è cercatori ActiveRecord.

User.find_by_email_and_age('[email protected]', 20) 

Non c'è, ovviamente, un metodo find_by_email_and_age. Invece, method_missing interrompe il nome, analizza le parti e invoca find con i parametri corretti.

9

È interamente sicuro da utilizzare a condizione che venga utilizzato nei modi previsti e non venga portato via. Non tutto quello che puoi fare vale la pena, dopo tutto.

Il vantaggio di method_missing è la possibilità di rispondere a tutti i tipi di cose in modi unici.

Lo svantaggio è che non pubblicizzate le vostre capacità. Altri oggetti che ti aspettano a respond_to? qualcosa non riceveranno la conferma e potrebbero trattare il tuo oggetto personalizzato in modi che non hai intenzione.

Per la costruzione di linguaggi specifici del dominio e la fornitura di colla molto lenta tra i componenti, questo genere di cose è inestimabile.

Un ottimo esempio di dove questa è una buona misura è la classe Ruby OpenStruct.

+1

+1 per aver menzionato il fratello gemello di 'method_missing'' responds_to? ' – akuhn

+7

In realtà, il fratello gemello di 'method_missing' è' respond_to_missing?' Dovresti * sempre * sovrascriverli entrambi. –

+0

@ JörgWMittag non ha mai sentito parlare di 'respond_to_missing' è una gemma !? – akuhn

4

Ecco uno dei miei preferiti

class Hash 
    def method_missing(sym,*args) 
    fetch(sym){fetch(sym.to_s){super}} 
    end 
end 

Il che mi lascia valori di accesso di un hash come se fossero attributi. Questo è particolarmente utile quando si lavora con dati JSON.

Quindi, ad esempio, piuttosto che dover scrivere tweets.collect{|each|each['text']}, posso semplicemente scrivere tweets.collect(&:text) che è molto più breve. O anche, invece di tweets.first['author'], posso semplicemente scrivere tweets.first.author che sembra molto più naturale. In realtà, ti dà accesso in stile Javascript ai valori di un hash.

 

NB, mi aspetto la scimmia patching polizia alla mia porta da un momento all'altro ...

+1

Si potrebbe anche scrivere un metodo che emetta procedure, come 'tweets.collect (& attr ('text'))' che sarebbe: 'def attr (a); lambda {| e | e [a]}; fine' – tadman

+0

Hai ragione, ma poi non riesco a scrivere 'tweets.first.text' (aggiornato il mio post). – akuhn

+0

Invece di lanciare il tuo, perché non usare OpenStruct?Creare un oggetto "nudo" è difficile da fare da solo. Se hai chiavi come "hash", entrerà in conflitto con i metodi interni. – tadman

3

Ho anche trovato un post sul blog da (Paolo Perrotta), dove ha dimostrato quando usare method_missing:

class InformationDesk 
    def emergency 
    # Call emergency... 
    "emergency() called" 
    end 

    def flights 
    # Provide flight information... 
    "flights() called" 
    end 

    # ...even more methods 
end 

Verificare se è stato richiesto un servizio durante l'ora di pranzo.

class DoNotDisturb 
    def initialize 
    @desk = InformationDesk.new 
    end 

    def method_missing(name, *args) 
    unless name.to_s == "emergency" 
     hour = Time.now.hour 
     raise "Out for lunch" if hour >= 12 && hour < 14 
    end 

    @desk.send(name, *args) 
    end 
end 

# At 12:30... 
DoNotDisturb.new.emergency # => "emergency() called" 
DoNotDisturb.new.flights # ~> -:37:in `method_missing': Out for lunch (RuntimeError) 
3

Innanzitutto, attenersi al riassunto di Sergio Tulentsev.

Oltre a ciò, penso che guardare agli esempi sia il modo migliore per avere un'idea delle situazioni giuste e sbagliate per method_missing; ecco è un altro semplice esempio: l'uso


Recentemente ho fatto di method_missing in un Null Object.

  • L'Oggetto Nullo sostituiva un modello Ordine.

  • L'ordine memorizza prezzi diversi per diverse valute.


Senza method_missing Sembra che questo:

class NullOrder 
    def price_euro 
    0.0 
    end 

    def price_usd 
    0.0 
    end 

    # ... 
    # repeat for all other currencies 
end 

Con method_missing, posso accorciare a:

class NullOrder 
    def method_missing(m, *args, &block) 
    m.to_s =~ /price_/ ? 0.0 : super 
    end 
end 

con l'ulteriore vantaggio di non avere a (ricordarsi di) aggiornare lo NullOrder quando aggiungo nuovi attributi price_xxx a Order.

Problemi correlati