Méthodes de module privé dans Ruby

108

J'ai une question en deux parties

Meilleur entrainement

  • J'ai un algorithme qui effectue une opération sur une structure de données à l'aide de l'interface publique
  • Il s'agit actuellement d'un module avec de nombreuses méthodes statiques, toutes privées à l'exception de la méthode d'interface publique unique.
  • Il y a une variable d'instance qui doit être partagée entre toutes les méthodes.

Voici les options que je peux voir, quelle est la meilleure?:

  • Module avec méthodes statiques ('module' en ruby)
  • Classe avec méthodes statiques
  • Module Mixin pour inclusion dans la structure de données
  • Refactoriser la partie de l'algorithme qui modifie cette structure de données (très petite) et en faire un mixin qui appelle les méthodes statiques du module d'algorithme

Partie technique

Existe-t-il un moyen de créer une méthode Module privée ?

module Thing
  def self.pub; puts "Public method"; end
  private
  def self.priv; puts "Private method"; end
end

Le privatededans ne semble pas avoir d'effet , je peux toujours appeler Thing.privsans problème.

Daniel Beardsley
la source
5
Pour info, il n'y a pas de méthode `` statique '' dans ruby, elles sont appelées méthodes d'instance de classe
brad
31
Un vieux commentaire, mais comme il a quatre votes positifs, je dois souligner qu'il n'existe pas de «méthode d'instance de classe». «Méthode de classe» est le terme correct.
micapam
5
privaten'affecte que les méthodes d'instance, pas les méthodes de classe. utiliser à la private_class_methodplace:module Thing; def self.pub; end; private_class_method :pub; end
apeiros
1
Les méthodes d'instance de classe @micapam existent dans Ruby et sont différentes des méthodes de classe.
Marnen Laibow-Koser le

Réponses:

88

Je pense que la meilleure façon (et surtout la façon dont les bibliothèques existantes sont écrites) pour ce faire est de créer une classe dans le module qui traite toute la logique, et le module fournit simplement une méthode pratique, par exemple

module GTranslate
  class Translator
    def perform( text ); translate( text ); end

    private

    def translate( text )
      # do some private stuff here
    end
  end

  def self.translate( text )
    t = Translator.new
    t.perform( text )
  end
end
ucron
la source
14
Ruby newb ici. Dans cet exemple, la classe Translator est-elle exposée dans le cadre de l'interface publique du module? La méthode 'perform' peut-elle avoir son accès limité à GTranslate?
rshepherd le
2
@rshepherd Le performn'est pas la méthode qui est censée être privée ici, la méthode privée est la méthode privée de la Translatorclasse (l'exemple de @ ucron n'en a pas, ce qui est très malheureux). GTranslate.translateest seulement une méthode pratique pour GTranslate::Translator#perform, il n'y a pas de réel gain qui la dissimule, si c'était possible.
michelpm
28
Je ne suis pas sûr de ce qui est accompli en ayant une classe ici. Si l'objectif est d'avoir une méthode de module privé, cela ne répond pas à l'objectif. Parce que vous pouvez accéder à la méthode "perform" depuis l'extérieur du module en appelant GTranslate :: Translator.new.perform. En d'autres termes, ce n'est pas privé.
Zack Xu
1
@jschorr Je pense que l'Op et cette réponse ont l'intention de créer une méthode de classe ou de module privée, pas une méthode d'instance. De plus, cela ne rendra aucune méthode d'instance privée car elle self.translatedéclare une méthode de classe / module.
konsolebox le
5
GTranslate::Translator.new.perform(text)- alambiqué, mais pas privé!
abhillman
80

Il y a aussi Module.private_class_method, qui exprime sans doute plus d'intention.

module Foo
  def self.included(base)
    base.instance_eval do
      def method_name
        # ...
      end
      private_class_method :method_name
    end
  end
end

Pour le code de la question:

module Thing
  def self.pub; puts "Public method"; end
  def self.priv; puts "Private method"; end
  private_class_method :priv
end

Ruby 2.1 ou plus récent:

module Thing
  def self.pub; puts "Public method"; end
  private_class_method def self.priv; puts "Private method"; end
end
konsolebox
la source
Je n'étais pas au courant de cela. Cela fonctionnera-t-il aussi avant la définition de la méthode, comme private?
Marnen Laibow-Koser
7
Cette réponse avec la réponse de @ JCooper est la vraie solution. @ MarnenLaibow-Koser Ce n'est pas le cas. Vous pouvez considérer l'autre réponse au prix de plus de regroupements et d'indentations. Cela peut en fait être la solution préférée pour certains. (Réponse juste pour référence.)
konsolebox
58
module Writer
  class << self
    def output(s)
      puts upcase(s)
    end

    private

    def upcase(s)
      s.upcase
    end
  end
end

Writer.output "Hello World"
# -> HELLO WORLD

Writer.upcase "Hello World"
# -> so.rb:16:in `<main>': private method `upcase' called for Writer:Module (NoMethodError)
cdrev
la source
4
Cela devrait être la réponse acceptée. Propre et idiomatique à mon avis.
Martin Nyaga
@MartinNyaga cela a l'inconvénient de ne pas avoir d' include Writeroption!
Ulysse BN le
28

Vous pouvez utiliser la méthode "inclus" pour faire des choses fantaisistes quand un module est mélangé. Cela fait ce que vous voulez, je pense:

module Foo
  def self.included(base)
    class << base 
      def public_method
        puts "public method"
      end
      def call_private
        private_method
      end
      private
      def private_method
        puts "private"
      end
    end
  end
end

class Bar
  include Foo
end

Bar.public_method

begin
  Bar.private_method
rescue
  puts "couldn't call private method"
end

Bar.call_private
Prix ​​Cameron
la source
5
C'est malin. C'est donc possible, mais probablement pas la peine.
Daniel Beardsley
fonctionne bien. J'ai utilisé included do |base| [...] endau lieu de def
Crystark
5
@Crystark: Cette syntaxe n'existe que sur les modules qui étendent ActiveSupport :: Concern si je ne me trompe pas. c'est à dire que c'est une chose de rails.
Vaz
11

Malheureusement, privatene s'applique qu'aux méthodes d'instance. La manière générale d'obtenir des méthodes "statiques" privées dans une classe est de faire quelque chose comme:

class << self
  private

  def foo()
   ....
  end
end

Certes, je n'ai pas joué avec cela dans les modules.

J Cooper
la source
7
Ce n'est pas vrai. Vous pouvez avoir des méthodes de classe privées et des méthodes de module privé.
mikeycgto
Vous pouvez avoir des méthodes de classe privées, mais cela ne fera pas .fooune méthode de classe privée: "private; def self.foo ()"
Ari
@mikeycgto Voulez-vous élaborer la différence entre les méthodes de classe privée et les méthodes de module privé? Parce que je pense qu'ils sont identiques. Notez que les deux privateet private_class_methodappartiennent à Modulenot Class. Ce code fonctionne d'ailleurs et c'est l'alternative à l'utilisation private_class_method.
konsolebox
3

Une belle façon est comme ça

module MyModule
  class << self
    def public_method
      # you may call the private method here
      tmp = private_method
      :public
    end

    private def private_method
      :private
    end
  end
end

# calling from outside the module
puts MyModule::public_method
Tallak Tveide
la source
1

Qu'en est-il du stockage de méthodes en tant que lambdas dans des variables / constantes de classe?

module MyModule
  @@my_secret_method = lambda {
    # ...
  }
  # ...
end

Pour le test:
UPD: une énorme mise à jour de ce code après 6 ans montre un moyen plus propre de déclarer une méthode privéed

module A
  @@L = lambda{ "@@L" }
  def self.a ; @@L[] ; end
  def self.b ; a ; end

  class << self
    def c ; @@L[] ; end
    private
    def d ; @@L[] ; end
  end
  def self.e ; c ; end
  def self.f ; self.c ; end
  def self.g ; d ; end
  def self.h ; self.d ; end

  private
  def self.i ; @@L[] ; end
  class << self
    def j ; @@L[] ; end
  end

  public
  def self.k ; i ; end
  def self.l ; self.i ; end
  def self.m ; j ; end
  def self.n ; self.j ; end
end

for expr in %w{ A.a A.b A.c A.d A.e A.f A.g A.h A.i A.j A.k A.l A.m A.n }
  puts "#{expr} => #{begin ; eval expr ; rescue => e ; e ; end}"
end

Ici, nous voyons que:

A.a => @@L
A.b => @@L
A.c => @@L
A.d => private method `d' called for A:Module
A.e => @@L
A.f => @@L
A.g => @@L
A.h => private method `d' called for A:Module
A.i => @@L
A.j => @@L
A.k => @@L
A.l => @@L
A.m => @@L
A.n => @@L

1) @@Lne peut pas être accessible de l'extérieur mais est accessible de presque partout
2) class << self ; private ; defrend la méthode dinaccessible de l'extérieur et de l'intérieur avec self.mais pas sans - c'est bizarre
3) private ; self.et private ; class << selfne rend pas les méthodes privées - elles sont accessibles à la fois Avec et sansself.

Nakilon
la source
Les lambdas ne sont pas du tout la même chose que les méthodes. les lambdas sont de type Proc, tandis que les méthodes sont de type Method.
Michael Dorst
1
les variables globales sont mauvaises
achempion
@achempion, où les voyez-vous?
Nakilon
@Nakilon mes excuses, modifiez votre réponse si vous voulez que j'annule mon vote
achempion
0

Créer un module ou un cours privé

Les constantes ne sont jamais privées. Cependant, il est possible de créer un module ou une classe sans l'affecter à une constante.

Donc, une alternative à :private_class_methodest de créer un module ou une classe privée et de définir des méthodes publiques dessus.

module PublicModule
  def self.do_stuff(input)
    @private_implementation.do_stuff(input)
  end

  @private_implementation = Module.new do
    def self.do_stuff(input)
      input.upcase # or call other methods on module
    end
  end
end

Usage:

PublicModule.do_stuff("whatever") # => "WHATEVER"

Consultez la documentation pour Module.new et Class.new .

Nathan Long
la source
J'aime vraiment cette méthode. Mais il ne semble pas possible de supprimer le .selfdans les définitions de méthode, de l'inclure dans une autre classe et de les utiliser comme instance_methods de la classe d'inclusion. Savez-vous s'il existe un moyen de le faire fonctionner?
Shiyason
0

Cette méthode n'autorise pas le partage de données avec les méthodes privées, sauf si vous transmettez explicitement les données par des paramètres de méthode.

module Thing
  extend self

  def pub
    puts priv(123)
  end

  private
  
  def priv(value)
    puts "Private method with value #{value}"
  end
end

Thing.pub
# "Private method with value 123"

Thing.priv
# NoMethodError (private method `priv' called for Thing:Module)
Gerry Shaw
la source