6
votes

Manière élégante des chaînes de typage de canard, des symboles et des tableaux?

Ceci est pour une API publique déjà existante que je ne peux pas casser, mais que je souhaite étendre.

Actuellement, la méthode prend une chaîne ou un symbole ou quelque chose d'autre qui a du sens lorsqu'il est passé comme le premier paramètre à envoyer

J'aimerais ajouter la possibilité d'envoyer une liste de chaînes, symboles, et cetera. Je pourrais simplement utiliser is_a? Array , mais il existe d'autres façons d'envoyer des listes, et ce n'est pas très rubis-ish.

Je vais appeler mapper sur la liste, la première inclination est d'utiliser réponse_to? : Carte . Mais une chaîne répond également à : mappe , afin que cela ne fonctionne pas.


4 commentaires

Rien à voir avec votre question, mais j'espère que vous faites des chèques avant de passer la saisie de l'utilisateur à la méthode d'envoi! Yikes.


bon point. Dans notre cas, les docs indiquent clairement que le paramètre est un nom de méthode. C'est une API assez profonde que cela devrait être suffisant ....


Ce n'est pas une question de documentation - c'est un trou de sécurité. S'ils passent dans une backtitude, l'envoi du deuxième paramètre est exécuté dans une coquille. Est-ce vraiment ce que vous voulez? Vous ferez beaucoup mieux à examiner l'entrée dans une déclaration de cas, puis envoyez des symboles de votre propre construction à envoyer.


Ce n'est qu'un trou de sécurité s'il est accessible à l'extérieur. C'est l'API d'une bibliothèque et non d'une application. S'ils veulent une coquille, c'est leur problème.


9 Réponses :


0
votes

Pouvez-vous simplement changer de comportement basé sur le paramètre.class.name? C'est moche, mais si je comprends correctement, vous avez une seule méthode que vous passerez plusieurs types à - vous devrez différencier d'une manière ou d'une autre.

Alternativement, ajoutez simplement une méthode qui gère un paramètre de type de tableau. C'est un comportement légèrement différent, une méthode supplémentaire pourrait donc avoir un sens.


2 commentaires

Votre deuxième commentaire est une meilleure solution, mais j'espère plus que des gourus Stackoverflow. Le premier a le même problème que is_a? Array : Un utilisateur peut envoyer une chaîne qui n'est pas une chaîne ou une liste qui n'est pas un tableau .


Je ne sais pas à quel point les cordes strings sont cependant. NON Array Les listes sont assez courantes - de nombreux objets sont énumérables . Malheureusement, les cordes ...



0
votes

Utilisez maréchal pour sérialiser vos objets avant de les envoyer. < / p>


1 commentaires

Je suis confus. Envoyer est la méthode qui appelle une fonction par nom dans Ruby. Comment aidera le maréchal?



1
votes

Depuis Array et String sont tous les deux énumérables , il n'y a pas un moyen élégant de dire "une chose qui est une chaîne enuméroable, mais pas une chaîne , "du moins pas dans la voie en train d'être discuté.

Ce que je ferais, c'est du type de canard pour être énumérable ( répond_to ?: [] ) puis utilisez un cas Déclaration, comme: xxx

ou même nettoyant: xxx


1 commentaires

Merci pour l'explication étendue, mais dans la question initiale, j'ai demandé quelque chose de nettoyeur que is_a? Array . En utilisant is_a? String (ou une variante comme si vous avez montré) enfreint l'API d'origine pour personne qui utilise quelque chose qui utilise quelque chose comme une chaîne pour envoyer . En fait, la plupart des utilisateurs utilisent probablement symbole à la place. Je pourrais utiliser x.is_a? (String) || x.is_a? (Symbole) , mais ce n'est pas très ruby-ish, et peut casser l'API pour certains de mes utilisateurs.



0
votes

Si vous ne voulez pas que vous ne voulez pas soinyyypatch, massez simplement la liste à une chaîne appropriée avant l'envoi. Si cela ne vous dérange pas de soinyyypatching ou héritant, mais que vous voulez garder la même méthode Signature:

class ToBePatched
    alias_method :__old_takes_a_string, :takes_a_string

    #since the old method wanted only a string, check for a string and call the old method
    # otherwise do your business with the map on things that respond to a map.
    def takes_a_string( string_or_mappable )
        return __old_takes_a_string( string_or_mappable ) if String === string_or_mappable
        raise ArgumentError unless string_or_mappable.responds_to?( :map )
        # do whatever you wish to do
    end
end


0 commentaires

1
votes

Peut-être que la question n'était peut-être pas assez claire, mais une nuit de sommeil m'a montré deux façons propres pour répondre à cette question.

1: to_sym code> est disponible sur String code> et symbole code> et doit être disponible sur tout ce que les charcelets comme une chaîne. p> xxx pré>

2: Envoyer des lancers TypeError CODE> Un tableau. P>

begin
  obj.send(arg, ...)
rescue TypeError
  # do array stuff
end


3 commentaires

La première solution a du sens, puisque l'envoi attend effectivement un symbole. Mais j'espère que vous n'êtes pas sérieux à propos de la deuxième solution. Les exceptions ne sont pas un flux de contrôle.


@Sarah: aléatoire, mais je pense qu'environ 40% de tous vos commentaires et réponses à propos de la phrase "Les exceptions ne sont pas du flux de contrôle".


@Telemachus - au moins récemment. :)



1
votes

Disons que votre fonction est nommée Func

Je ferais un tableau des paramètres avec P>

def func(param)
  a = Array.new
  a << param
  a.flatten!
  func_array(a)
end


0 commentaires

6
votes

Pourquoi ne pas les traiter tous comme des tableaux ? Le comportement que vous souhaitez pour string s est identique à celui d'un tableau contenant uniquement cette chaîne : xxx

Le [* arg] Trick fonctionne car l'opérateur Splat ( * ) active un seul élément en lui-même ou un tableau dans une liste intégrée de Ses éléments.

plus tard

Ceci est essentiellement juste une version syntaxiquement sucrée ou Réponse d'Arnaud , bien qu'il existe des différences subtiles si vous passez un tableau contenant d'autres Array s.

plus tard toujours

Il y a une différence supplémentaire à voir avec FOO " S Valeur de retour. Si vous appelez foo (bar ,: baz) , vous serez peut-être surpris d'obtenir [baz] retour. Pour résoudre ce problème, vous pouvez ajouter un Kestrel : xxx

qui retournera toujours arg comme passé. Ou vous pouvez faire retourner (obj) afin que vous puissiez chaîner des appels vers foo . C'est à vous de décider quel type de comportement de retour de valeur que vous voulez.


1 commentaires

Beaucoup plus beau que mon post original. Je ne peux jamais m'habituer à l'opérateur *.



3
votes

Un détail critique qui a été négligé dans toutes les réponses: Strings Do pas répondez à : mappe , la réponse la plus simple est donc dans la question initiale: utilisez simplement < code> répondre_to? : Carte .


0 commentaires

0
votes

entre ces 3 types, je ferais ce que

is_array = var.respond_to?(:to_h)
is_string = var.respond_to?(:each_char)
is_symbol = var.respond_to?(:to_proc)


0 commentaires