Existe-t-il un moyen d'appeler une méthode de classe privée à partir d'une instance dans Ruby ?
-
09-06-2019 - |
Question
Autre que self.class.send :method, args...
, bien sûr.J'aimerais rendre une méthode plutôt complexe disponible à la fois au niveau de la classe et de l'instance sans dupliquer le code.
MISE À JOUR:
@Jonathan Branam :c'était mon hypothèse, mais je voulais m'assurer que personne d'autre n'avait trouvé un moyen de contourner.La visibilité en Ruby est très différente de celle en Java.Tu as aussi tout à fait raison de dire que private
ne fonctionne pas sur les méthodes de classe, même si cela déclarera une méthode de classe privée :
class Foo
class <<self
private
def bar
puts 'bar'
end
end
end
Foo.bar
# => NoMethodError: private method 'bar' called for Foo:Class
La solution
Voici un extrait de code pour accompagner la question.L'utilisation de « privé » dans une définition de classe ne s'applique pas aux méthodes de classe.Vous devez utiliser "private_class_method" comme dans l'exemple suivant.
class Foo
def self.private_bar
# Complex logic goes here
puts "hi"
end
private_class_method :private_bar
class <<self
private
def another_private_bar
puts "bar"
end
end
public
def instance_bar
self.class.private_bar
end
def instance_bar2
self.class.another_private_bar
end
end
f=Foo.new
f=instance_bar # NoMethodError: private method `private_bar' called for Foo:Class
f=instance_bar2 # NoMethodError: private method `another_private_bar' called for Foo:Class
Je ne vois pas de moyen de contourner ce problème.La documentation indique que vous ne pouvez pas spécifier la réception d'une méthode privée.De plus, vous ne pouvez accéder qu'à une méthode privée à partir de la même instance.La classe Foo est un objet différent d'une instance donnée de Foo.
Ne prenez pas ma réponse comme définitive.Je ne suis certainement pas un expert, mais je voulais fournir un extrait de code afin que ceux qui tentent de répondre disposent de méthodes de classe correctement privées.
Autres conseils
Permettez-moi de contribuer à cette liste de solutions et non-solutions plus ou moins étranges :
puts RUBY_VERSION # => 2.1.2
class C
class << self
private def foo
'Je suis foo'
end
end
private define_method :foo, &method(:foo)
def bar
foo
end
end
puts C.new.bar # => Je suis foo
puts C.new.foo # => NoMethodError
Si votre méthode est simplement un fonction d'utilité (c'est-à-dire qu'elle ne repose sur aucune variable d'instance), vous pouvez mettre la méthode dans un module et include
et extend
la classe afin qu'elle soit disponible à la fois en tant que méthode de classe privée et en tant que méthode d'instance privée.
De nos jours, vous n'avez plus besoin des méthodes d'assistance.Vous pouvez simplement les intégrer à la définition de votre méthode.Cela devrait sembler très familier aux utilisateurs de Java :
class MyClass
private_class_method def self.my_private_method
puts "private class method"
end
private def my_private_method
puts "private instance method"
end
end
Et non, vous ne pouvez pas appeler une méthode de classe privée à partir d’une méthode d’instance.Cependant, vous pouvez plutôt implémenter le privé méthode de classe comme publique méthode de classe dans un privé classe imbriquée à la place, en utilisant la private_constant
méthode d'assistance.Voir cet article de blog pour plus de détails.
C'est ainsi que l'on peut jouer avec les "vraies" méthodes de classe privée.
class Foo
def self.private_bar
# Complex logic goes here
puts "hi"
end
private_class_method :private_bar
class <<self
private
def another_private_bar
puts "bar"
end
end
public
def instance_bar
self.class.private_bar
end
def instance_bar2
self.class.another_private_bar
end
def calling_private_method
Foo.send :another_private_bar
self.class.send :private_bar
end
end
f=Foo.new
f.send :calling_private_method
# "bar"
# "hi"
Foo.send :another_private_bar
# "bar"
acclamations
C'est probablement la méthode la plus "native vanilla Ruby":
class Foo
module PrivateStatic # like Java
private def foo
'foo'
end
end
extend PrivateStatic
include PrivateStatic
def self.static_public_call
"static public #{foo}"
end
def public_call
"instance public #{foo}"
end
end
Foo.static_public_call # 'static public foo'
Foo.new.public_call # 'instance public foo'
Foo.foo # NoMethodError: private method `foo' called for Foo:Class
Foo.new.foo # NoMethodError: private method `foo' called for #<Foo:0x00007fa154d13f10>
Avec un peu de métaprogrammation Ruby, vous pourriez même faire ressembler cela à :
class Foo
def self.foo
'foo'
end
extend PrivateStatic
private_static :foo
end
La métaprogrammation de Ruby est assez puissante, vous pouvez donc techniquement implémenter toutes les règles de portée que vous souhaitez.Cela étant dit, je préférerais quand même la clarté et surprise minime de la première variante.
Sauf erreur de compréhension, n'avez-vous pas simplement besoin de quelque chose comme ceci :
class Foo
private
def Foo.bar
# Complex logic goes here
puts "hi"
end
public
def bar
Foo.bar
end
end
Bien sûr, vous pouvez modifier la deuxième définition pour utiliser votre approche self.class.send si vous souhaitez éviter de coder en dur le nom de la classe...