Chamando um método de uma string com o nome do método em Ruby

157

Como posso fazer o que eles estão falando aqui , mas em Ruby?

Como você faria a função em um objeto? e como você faria uma função global (veja a resposta do jetxee no post mencionado)?

CÓDIGO DO EXEMPLO:

event_name = "load"

def load()
  puts "load() function was executed."
end

def row_changed()
  puts "row_changed() function was executed."
end 

#something here to see that event_name = "load" and run load()

UPDATE: Como você chega aos métodos globais? ou minhas funções globais?

Eu tentei esta linha adicional

puts methods

e load e row_change onde não estiver listado.

BuddyJoe
fonte

Respostas:

232

Para chamar funções diretamente em um objeto

a = [2, 2, 3]
a.send("length")
# or
a.public_send("length")

que retorna 3 conforme o esperado

ou para uma função de módulo

FileUtils.send('pwd')
# or
FileUtils.public_send(:pwd)

e um método definido localmente

def load()
    puts "load() function was executed."
end

send('load')
# or
public_send('load')

Documentação:

Colin Gravill
fonte
3
+1 Isso funciona. Pode ser um acompanhamento idiota ... mas como é que não consigo encontrar a palavra send na fonte Ruby em - C: \ ruby ​​\ lib \ ruby ​​\ 1.8 \ fileutils.rb? Pensei em encontrar a função de envio lá.
BuddyJoe 10/09/09
1
Eu estava curioso para o que estava fazendo sob o capô.
BuddyJoe 10/09/09
Está definido no objeto - ruby-doc.org/core/classes/Object.html#M000332 Escolhi uma função aleatória para o valor do interesse.
Colin Gravill 10/09/09
1
Interessante, porque antes de ler sua resposta duas vezes e grocá-la completamente, executei o FileUtils.send ("load") e ele executou minha função. então, se eu entendi isso corretamente criando funções em "global", estou adicionando os métodos ao objeto raiz? ou não?
BuddyJoe 10/09/09
12
Bom em você por procurar coisas na fonte! :)
Colin Gravill 10/09/09
34

Três maneiras: send/ call/ eval- e seus benchmarks

Chamada típica (para referência):

s= "hi man"
s.length #=> 6

Usando send

s.send(:length) #=> 6

Usando call

method_object = s.method(:length) 
p method_object.call #=> 6

Usando eval

eval "s.length" #=> 6

 

Benchmarks

require "benchmark" 
test = "hi man" 
m = test.method(:length) 
n = 100000 
Benchmark.bmbm {|x| 
  x.report("call") { n.times { m.call } } 
  x.report("send") { n.times { test.send(:length) } } 
  x.report("eval") { n.times { eval "test.length" } } 
} 

... como você pode ver, instanciar um objeto de método é a maneira dinâmica mais rápida de chamar um método, observe também o quão lento é o uso de eval.

#######################################
#####   The results
#######################################
#Rehearsal ----------------------------------------
#call   0.050000   0.020000   0.070000 (  0.077915)
#send   0.080000   0.000000   0.080000 (  0.086071)
#eval   0.360000   0.040000   0.400000 (  0.405647)
#------------------------------- total: 0.550000sec

#          user     system      total        real
#call   0.050000   0.020000   0.070000 (  0.072041)
#send   0.070000   0.000000   0.070000 (  0.077674)
#eval   0.370000   0.020000   0.390000 (  0.399442)

O crédito vai para este post do blog que detalha um pouco mais os três métodos e também mostra como verificar se os métodos existem.

cwd
fonte
Eu estava apenas descobrindo isso e notei algo que não estava coberto. O que eu faria se quisesse Class.send("classVariable") = 5? Isso gera um erro. Existe alguma maneira de contornar isso? O mesmo se aplica ao uso de # Class.method("classVariable").call() = 5
thesecretmaster #
se você quiser enviar alguma discussão com o uso de chamadas send algo assim ClassName.send ( "method_name", arg1, arg2)
Aleem
Seu benchmark não deve callincluir a instanciação do método object ( m.test.method(:length)) para representar com precisão que é a hora certa? Ao usar, callvocê provavelmente instancia o objeto de método sempre.
Joshua Pinter
O link da postagem do blog está morto, btw.
Joshua Pinter
33

Usa isto:

> a = "my_string"
> meth = a.method("size")
> meth.call() # call the size method
=> 9

Simples, certo?

Quanto ao global , acho que o caminho do Ruby seria pesquisá-lo usando o methodsmétodo

Geo
fonte
meth = a.method? essa chamada já não é a função? e se o método retornar alguma coisa?
user1735921
FYI para quando o método não existe: "my_string".method('blah') #=> NameError: undefined method blá' para a classeString'
Joshua Pinter
3

Pessoalmente, eu configuraria um hash para funcionar referências e, em seguida, usaria a string como um índice para o hash. Você chama a referência da função com seus parâmetros. Isso tem a vantagem de não permitir que a string errada chame algo que você não deseja chamar. A outra maneira é basicamente evala string. Não faça isso.

O PS não seja preguiçoso e, na verdade, digite toda a sua pergunta, em vez de vincular a algo.

dlamblin
fonte
Desculpe. Vou copiar algumas palavras e traduzi-las para torná-las específicas para Ruby. 1
BuddyJoe 10/09/09