J'ai beaucoup entendu parler de Ruby pour ses capacités de métaprogrammation super spectaculaires, et je me demandais si quelqu'un pouvait m'aider à résoudre ce problème.Passer à la métaprogrammation Ruby: Générer des méthodes proxy pour plusieurs méthodes internes
J'ai une classe qui fonctionne comme une sorte d '"archive", avec des méthodes internes qui traitent et produisent des données basées sur une entrée. Cependant, les éléments de l'archive de la classe elle-même sont représentés et traités avec des entiers, à des fins de performance. Les éléments réels en dehors de l'archive sont connus par leur représentation sous forme de chaîne, qui est simplement number_representation.to_s (36). Pour cette raison, j'ai raccordé chaque méthode interne avec une "méthode proxy" qui convertit l'entrée dans la forme entière que l'archive reconnaît, exécute la méthode interne et convertit la sortie (soit un seul autre élément, ou une collection d'entre eux) de retour en chaînes.
La convention de dénomination est la suivante: les méthodes internes sont représentées par _method_name; leur méthode de proxy correspondante est représentée par nom_méthode, sans trait de soulignement.
Par exemple:
class Archive
## PROXY METHODS ##
## input: string representation of id's
## output: string representation of id's
def do_something_with id
result = _do_something_with id.to_i(36)
return nil if result == nil
return result.to_s(36)
end
def do_something_with_pair id_1,id_2
result = _do_something_with_pair id_1.to_i(36), id_2.to_i(36)
return nil if result == nil
return result.to_s(36)
end
def do_something_with_these ids
result = _do_something_with_these ids.map { |n| n.to_i(36) }
return nil if result == nil
return result.to_s(36)
end
def get_many_from id
result = _get_many_from id
return nil if result == nil # no sparse arrays returned
return result.map { |n| n.to_s(36) }
end
## INTERNAL METHODS ##
## input: integer representation of id's
## output: integer representation of id's
private
def _do_something_with id
# does something with one integer-represented id,
# returning an id represented as an integer
end
def do_something_with_pair id_1,id_2
# does something with two integer-represented id's,
# returning an id represented as an integer
end
def _do_something_with_these ids
# does something with multiple integer ids,
# returning an id represented as an integer
end
def _get_many_from id
# does something with one integer-represented id,
# returns a collection of id's represented as integers
end
end
Il y a deux raisons pour lesquelles je ne peux pas les convertir si id.class == chaîne au début des méthodes internes:
- Ces les méthodes internes sont des fonctions récursives un peu intensives en calcul, et je ne veux pas que le temps de contrôle soit multiplié à chaque étape.
- Il n'est pas possible, sans ajouter de paramètre supplémentaire, de dire s'il faut reconvertir à la fin
- Je veux penser à cela comme un exercice de compréhension ruby méta-programmation
Est-ce que quelqu'un a des idées?
modifier
La solution que je voudrais serait de préférence être en mesure de prendre un tableau de noms de méthode
@@PROXY_METHODS = [:do_something_with, :do_something_with_pair,
:do_something_with_these, :get_many_from]
itérer à travers eux, et à chaque itération, mettre la méthode de proxy. Je ne suis pas sûr de ce qui serait fait avec les arguments, mais y a-t-il un moyen de tester les arguments d'une méthode? Si ce n'est pas le cas, alors un simple typage de canard/un concept analogue ferait aussi bien.
Je suis venu avec ma propre solution, en utilisant #class_eval
@@PROXY_METHODS.each do |proxy|
class_eval %{ def #{proxy} *args
args.map! do |a|
if a.class == String
a.to_i(36)
else
a.map { |id| id.to_i(36) }
end
end
result = _#{proxy}(*args)
result and if result.respond_to?(:each)
result.map { |r| r.to_s(36) }
else
result.to_s(36)
end
end
}
end
Cependant, #class_eval
semble un peu ... désordre? ou inélégant par rapport à ce qu'il "devrait" être.
merci! Cela fonctionne très bien. Juste une petite question - est-ce important de créer une classe singleton, ou pourriez-vous simplement définir self.def_api_method (en termes de convention/style)? En outre, que voulez-vous dire par la dernière chose que vous avez écrite, sur l'utilisation de #send? Merci beaucoup! –
@mstksg: J'en ai ajouté quelques-unes sur la façon dont ça se ferait avec 'send'. Est-ce plus clair? – rampion
merci! Je crois que c'est exactement ce que je cherchais :) –