2012-02-09 11 views
74

Nuovo per Ruby e ROR e amarla ogni giorno, ecco la mia domanda visto che non ho idea di come google (e ho provato :))C'è un modo per accedere agli argomenti del metodo in Ruby?

abbiamo metodo

def foo(first_name, last_name, age, sex, is_plumber) 
    # some code 
    # error happens here 
    logger.error "Method has failed, here are all method arguments #{SOMETHING}"  
end 

Così quello che sto cercando per modo di ottenere tutti gli argomenti passati al metodo, senza elencare ciascuno. Poiché si tratta di Rubino suppongo ci sia un modo :) se fosse java vorrei solo elencare loro :)

uscita sarebbe:

Method has failed, here are all method arguments {"Mario", "Super", 40, true, true} 
+1

Reha kralj svegami! – ant

risposta

124

In Ruby 1.9.2 e versioni successive è possibile utilizzare il metodo parameters su un metodo per ottenere l'elenco dei parametri per tale metodo. Ciò restituirà un elenco di coppie che indicano il nome del parametro e se è richiesto.

ad es.

Se fai

def foo(x, y) 
end 

poi

method(:foo).parameters # => [[:req, :x], [:req, :y]] 

È possibile utilizzare la variabile speciale __method__ per ottenere il nome del metodo corrente.Quindi, all'interno di un metodo i nomi dei suoi parametri possono essere ottenuti tramite

args = method(__method__).parameters.map { |arg| arg[1].to_s } 

È quindi possibile visualizzare il nome e il valore di ogni parametro con

logger.error "Method failed with " + args.map { |arg| "#{arg} = #{eval arg}" }.join(', ') 

Nota: dato che questa risposta è stata originariamente scritto, nelle attuali versioni di Ruby eval non può più essere chiamato con un simbolo. Per far fronte a questo, un esplicito to_s è stato aggiunto quando si costruisce la lista dei nomi dei parametri cioè parameters.map { |arg| arg[1].to_s }

+4

Avrò bisogno di tempo per decifrarlo :) –

+2

Fammi sapere quali bit devono essere decifrati e aggiungerò qualche spiegazione :) – mikej

+3

+1 questo è davvero fantastico ed elegante; sicuramente la migliore risposta –

15

Un modo per gestire questa situazione è:

def foo(*args) 
    first_name, last_name, age, sex, is_plumber = *args 
    # some code 
    # error happens here 
    logger.error "Method has failed, here are all method arguments #{args.inspect}"  
end 
+0

Lavorando e sarà votato come accettato a meno che non ci siano risposte migliori, il mio unico problema con questo è che non voglio perdere la firma del metodo, alcuni ci saranno Inteli sense e odierò perderlo. –

6

Questa è una domanda interessante. Forse usando local_variables? Ma deve esserci un modo diverso dall'usare la valutazione. Sto cercando in Kernel doc

class Test 
    def method(first, last) 
    local_variables.each do |var| 
     puts eval var.to_s 
    end 
    end 
end 

Test.new().method("aaa", 1) # outputs "aaa", 1 
+0

Non è poi così male, perché questa soluzione del male? –

+0

In questo caso non è male: l'uso di eval() può talvolta portare a dei buchi di sicurezza. Solo penso che ci possa essere un modo migliore :) ma ammetto che Google non è il nostro amico in questo caso – Raffaele

+0

Ho intenzione di andare con questo, il rovescio della medaglia è che non puoi fare helper (modulo) che si prenderà cura di questo, dal momento che come appena lascia il metodo originale non può fare evals di vars locali. Grazie a tutti per informazioni. –

2

Prima di andare oltre, si sta passando troppi argomenti in foo. Sembra che tutti questi argomenti siano attributi su un modello, corretto? Dovresti davvero passare l'oggetto stesso. Fine del discorso

È possibile utilizzare un argomento "splat". Mette tutto in un array. Si sarebbe simile:

def foo(*bar) 
    ... 
    log.error "Error with arguments #{bar.joins(', ')}" 
end 
+0

Non sono d'accordo su questo, la firma del metodo è importante per la leggibilità e la riusabilità del codice.L'oggetto stesso va bene, ma devi creare l'istanza da qualche parte, quindi prima di chiamare il metodo o nel metodo. Meglio nel metodo, a mio parere. per esempio. creare il metodo utente. –

+0

Per citare un uomo più intelligente di me, Bob Martin, nel suo libro, Clean Code, "il numero ideale di argomenti per una funzione è zero (niladico). Dopo viene uno (monoadico), seguito da vicino da due (diadico). gli argomenti (triadica) dovrebbero essere evitati laddove possibile, più di tre (poliadici) richiedono una giustificazione molto speciale - e quindi non dovrebbero essere comunque utilizzati ". Continua dicendo quello che ho detto, molti argomenti correlati dovrebbero essere racchiusi in una classe e passati come un oggetto. È un buon libro, lo consiglio vivamente. –

+0

Non mettere troppo a fuoco un punto, ma considera questo: se trovi che hai bisogno di più/meno/argomenti diversi allora avrai rotto la tua API e dovrai aggiornare ogni chiamata a quel metodo. D'altra parte, se passi un oggetto, altre parti della tua app (o dei consumatori del tuo servizio) possono sbizzarrirsi allegramente. –

2

Se si desidera cambiare la firma del metodo, si può fare qualcosa di simile:

def foo(*args) 
    # some code 
    # error happens here 
    logger.error "Method has failed, here are all method arguments #{args}"  
end 

Oppure:

def foo(opts={}) 
    # some code 
    # error happens here 
    logger.error "Method has failed, here are all method arguments #{opts.values}"  
end 

In questo caso, interpolato args o opts.values sarà una matrice, ma è possibile join se sulla virgola. Cheers

2

Sembra che ciò che a questa domanda sta cercando di realizzare potrebbe essere fatto con una gemma Ho appena rilasciato, https://github.com/ericbeland/exception_details. Elencherà le variabili locali e le vlaue (e le variabili di istanza) dalle eccezioni salvate. Potrebbe valere la pena dare un'occhiata ...

+1

Questa è una bella gemma, per gli utenti di Rails vorrei anche raccomandare la gemma 'better_errors'. –

2

È possibile definire una costante come:

ARGS_TO_HASH = "method(__method__).parameters.map { |arg| arg[1].to_s }.map { |arg| { arg.to_sym => eval(arg) } }.reduce Hash.new, :merge" 

e utilizzarlo nel codice come:

args = eval(ARGS_TO_HASH) 
another_method_that_takes_the_same_arguments(**args) 
35

Dal Rubino 2.1 è possibile utilizzare per binding.local_variable_get leggere il valore di qualsiasi variabile locale, inclusi i parametri del metodo (argomenti). Grazie a ciò è possibile migliorare il accepted answer per evitare-malvagio eval.

def foo(x, y) 
    method(__method__).parameters.map do |_, name| 
    binding.local_variable_get(name) 
    end 
end 

foo(1, 2) # => 1, 2 
+0

è 2.1 il più presto? – uchuugaka

+0

@uchuugaka Sì, questo metodo non è disponibile in <2.1. –

+0

Grazie. Questo lo rende bello: logger.info __method __ + "# {args.inspect}" metodo (__ metodo __). Parameters.map do | _, name | logger.info "# {nome} =" + binding.local_variable_get (nome) fine –

3

Questo può essere utile ...

def foo(x, y) 
    args(binding) 
    end 

    def args(callers_binding) 
    callers_name = caller[0][/`.*'/][1..-2] 
    parameters = method(callers_name).parameters 
    parameters.map { |_, arg_name| 
     callers_binding.local_variable_get(arg_name) 
    }  
    end 
0

Se avete bisogno di argomenti come Hash, e non si vuole inquinare il corpo di metodo con estrazione complicata di parametri, utilizzare questo:

def mymethod(firstarg, kw_arg1:, kw_arg2: :default) 
    args = MethodArguments.(binding) # All arguments are in `args` hash now 
    ... 
end 

Basta aggiungere questa classe per il vostro progetto:

class MethodArguments 
    def self.call(ext_binding) 
    raise ArgumentError, "Binding expected, #{ext_binding.class.name} given" unless ext_binding.is_a?(Binding) 
    method_name = ext_binding.eval("__method__") 
    ext_binding.receiver.method(method_name).parameters.map do |_, name| 
     [name, ext_binding.local_variable_get(name)] 
    end.to_h 
    end 
end 
Problemi correlati