Appel d'une méthode à partir d'une chaîne avec le nom de la méthode dans Ruby

157

Comment puis-je faire ce dont ils parlent ici , mais en Ruby?

Comment feriez-vous la fonction sur un objet? et comment feriez-vous une fonction globale (voir la réponse de jetxee sur le post mentionné)?

EXEMPLE DE CODE:

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()

MISE À JOUR: Comment obtenez-vous les méthodes globales? ou mes fonctions globales?

J'ai essayé cette ligne supplémentaire

puts methods

et load et row_change ne figurant pas dans la liste.

BuddyJoe
la source

Réponses:

232

Pour appeler des fonctions directement sur un objet

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

qui renvoie 3 comme prévu

ou pour une fonction de module

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

et une méthode définie localement

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

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

Documentation:

Colin Gravill
la source
3
+1 Cela fonctionne. Cela peut être un suivi stupide ... mais comment se fait-il que je ne trouve pas le mot envoyer dans la source Ruby à - C: \ ruby ​​\ lib \ ruby ​​\ 1.8 \ fileutils.rb? Je pensais trouver la fonction d'envoi là-dedans.
BuddyJoe
1
J'étais curieux de savoir ce qu'il faisait sous le capot.
BuddyJoe
Il est défini sur l'objet - ruby-doc.org/core/classes/Object.html#M000332 J'ai choisi une fonction aléatoire pour la valeur d'intérêt.
Colin Gravill
1
Intéressant parce qu'avant de lire votre réponse deux fois et de l'avoir entièrement grok, j'ai lancé FileUtils.send ("load") et il a exécuté ma fonction. donc si je comprends cela correctement en créant des fonctions dans "global" est-ce que j'ajoute les méthodes sur l'objet racine? ou pas?
BuddyJoe
12
Bien à vous pour rechercher des trucs dans la source! :)
Colin Gravill
34

Trois façons: send/ call/ eval- et leurs points de repère

Appel typique (pour référence):

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

En utilisant send

s.send(:length) #=> 6

En utilisant call

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

En utilisant 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" } } 
} 

... comme vous pouvez le voir, instancier un objet de méthode est le moyen dynamique le plus rapide d'appeler une méthode, notez également la lenteur de l'utilisation 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)

Le mérite revient à cet article de blog qui explique un peu plus les trois méthodes et montre également comment vérifier si les méthodes existent.

cwd
la source
J'étais juste en train de trouver ceci, et j'ai remarqué quelque chose qui n'était pas couvert. Que ferais-je si je voulais faire Class.send("classVariable") = 5? Cela jette une erreur. Y a-t-il un moyen de contourner cela? La même chose est vraie pour l'utilisationClass.method("classVariable").call() = 5
thesecretmaster
si vous voulez envoyer un argument avec un appel d'envoi, utilisez quelque chose comme ce ClassName.send ("method_name", arg1, arg2)
Aleem
Votre benchmark ne devrait-il pas callinclure l'instanciation de la méthode object ( m.test.method(:length)) pour représenter avec précision son temps réel? Lors de l'utilisation, callvous allez probablement instancier l'objet de méthode à chaque fois.
Joshua Pinter
Le lien de l'article de blog est mort, btw.
Joshua Pinter
33

Utilisez ceci:

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

Simple, non?

En ce qui concerne le global , je pense que la méthode Ruby serait de le rechercher en utilisant la methodsméthode.

Géo
la source
meth = une.method? n'est-ce pas déjà appeler la fonction? et si la méthode renvoie quelque chose?
user1735921
FYI pour quand la méthode n'existe pas: "my_string".method('blah') #=> NameError: undefined method bla 'pour la classeString'
Joshua Pinter
3

Personnellement, je configurerais un hachage pour les références de fonction, puis j'utiliserais la chaîne comme index du hachage. Vous appelez ensuite la référence de fonction avec ses paramètres. Cela a l'avantage de ne pas permettre à la mauvaise chaîne d'appeler quelque chose que vous ne voulez pas appeler. L'autre moyen consiste essentiellement à utiliser evalla chaîne. Ne faites pas cela.

PS ne soyez pas paresseux et tapez votre question entière, au lieu de créer un lien vers quelque chose.

dlamblin
la source
Désolé. Je vais copier une partie du libellé et traduire pour le rendre spécifique à Ruby. +1
BuddyJoe