Métodos de módulos privados en Ruby

108

Tengo una pregunta de dos partes

Mejores prácticas

  • Tengo un algoritmo que realiza alguna operación en una estructura de datos usando la interfaz pública
  • Actualmente es un módulo con numerosos métodos estáticos, todos privados excepto el método de interfaz pública.
  • Hay una variable de instancia que debe compartirse entre todos los métodos.

Estas son las opciones que puedo ver, ¿cuál es la mejor ?:

  • Módulo con métodos estáticos ('módulo' en ruby)
  • Clase con métodos estáticos
  • Módulo Mixin para su inclusión en la estructura de datos
  • Refactorice la parte del algoritmo que modifica esa estructura de datos (muy pequeña) y conviértala en una mezcla que llame a los métodos estáticos del módulo de algoritmo

Parte técnica

¿Hay alguna forma de hacer un método de módulo privado ?

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

El privateno parece tener ningún efecto , todavía puedo llamar Thing.privsin problemas.

Daniel Beardsley
fuente
5
Para su información, no existe un método 'estático' en ruby, se llaman métodos de instancia de clase
brad,
31
Un comentario antiguo, pero como tiene cuatro votos a favor, debo señalar que no existe un 'método de instancia de clase'. "Método de clase" es el término correcto.
micapam
5
privatesolo afecta a los métodos de instancia, no a los métodos de clase. usar private_class_methoden su lugar:module Thing; def self.pub; end; private_class_method :pub; end
apeiros
1
@micapam Los métodos de instancia de clase existen en Ruby y son diferentes de los métodos de clase.
Marnen Laibow-Koser

Respuestas:

88

Creo que la mejor manera (y principalmente cómo se escriben las bibliotecas existentes) para hacer esto es creando una clase dentro del módulo que se ocupa de toda la lógica, y el módulo solo proporciona un método conveniente, por ejemplo

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
fuente
14
Ruby newb aquí. En este ejemplo, ¿se expone la clase Translator como parte de la interfaz pública del módulo? ¿Puede el método 'perform' tener su acceso restringido a GTranslate?
rshepherd
2
@rshepherd El performno es el método que se supone que es privado aquí, el método privado es el método privado en la Translatorclase (el ejemplo de @ucron no tiene ninguno, lo que es muy desafortunado). GTranslate.translatees sólo un método conveniente GTranslate::Translator#perform, no hay ninguna ganancia real que lo oculte, si es que fuera posible.
michelpm
28
No estoy seguro de qué se logra al tener una clase aquí. Si el objetivo es tener un método de módulo privado, entonces esto no cumple con el objetivo. Porque puede acceder al método "perform" desde fuera del módulo llamando a GTranslate :: Translator.new.perform. En otras palabras, no es privado.
Zack Xu
1
@jschorr Creo que Op y esta respuesta pretenden hacer una clase privada o un método de módulo, no un método de instancia. Además, eso no hará que ningún método de instancia sea privado, ya que self.translatedeclara un método de clase / módulo.
konsolebox
5
GTranslate::Translator.new.perform(text)- ¡Enrevesado, pero no privado!
abhillman
80

También está Module.private_class_method, que posiblemente expresa más intención.

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

Para el código en la pregunta:

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

Ruby 2.1 o más reciente:

module Thing
  def self.pub; puts "Public method"; end
  private_class_method def self.priv; puts "Private method"; end
end
konsolebox
fuente
No estaba consciente de esto. ¿Funcionará también antes de la definición del método private?
Marnen Laibow-Koser
7
Esta respuesta junto con la respuesta de @ JCooper es la solución real. @ MarnenLaibow-Koser No es así. Puede considerar la otra respuesta a costa de más agrupaciones y sangrías. De hecho, puede ser la solución preferida para algunos. (Respondiendo solo como referencia.)
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
fuente
4
Esta debería ser la respuesta aceptada. Limpio e idiomático en mi opinión.
Martin Nyaga
@MartinNyaga ¡esto tiene el inconveniente de no tener include Writeropción!
Ulysse BN
28

Puede usar el método "incluido" para hacer cosas elegantes cuando se mezcla un módulo. Esto hace lo que quiere, creo:

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
Precio de Cameron
fuente
5
Eso es inteligente. Entonces es posible, pero probablemente no valga la pena.
Daniel Beardsley
funciona bien. He utilizado included do |base| [...] enden lugar de DEF
Crystark
5
@Crystark: Esa sintaxis solo existe en módulos que amplían ActiveSupport :: Concern si no me equivoco. es decir, es una cosa de rieles.
Vaz
11

Desafortunadamente, privatesolo se aplica a los métodos de instancia. La forma general de obtener métodos privados "estáticos" en una clase es hacer algo como:

class << self
  private

  def foo()
   ....
  end
end

Es cierto que no he jugado con hacer esto en módulos.

J Cooper
fuente
7
Esto no es verdad. Puede tener métodos de clases privados y métodos de módulos privados.
mikeycgto
Puede tener métodos de clase privados, pero simplemente hacer esto no hará .fooun método de clase privado: "privado; def self.foo ()"
Ari
@mikeycgto ¿Le importaría explicar la diferencia entre los métodos de clases privadas y los métodos de módulos privados? Porque creo que son iguales. Tenga en cuenta que ambos privatey private_class_methodson propiedad de Modulenot Class. Por cierto, este código funciona y es la alternativa a usar private_class_method.
konsolebox
3

Una buena forma es así

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
fuente
1

¿Qué pasa con el almacenamiento de métodos como lambdas dentro de variables / constantes de clase?

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

Para la prueba:
UPD: la gran actualización de este código después de 6 años muestra una forma más limpia de declarar un método privadod

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

Aquí vemos 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) @@Lno se puede acceder desde el exterior pero es accesible desde casi todas partes
2) class << self ; private ; defhace que el método sea dinaccesible desde afuera y desde adentro con self.pero no sin él - esto es extraño
3) private ; self.y private ; class << selfno hace que los métodos sean privados - ambos son accesibles con y sinself.

Nakilon
fuente
lambdas no son en absoluto lo mismo que métodos. lambdas son de tipo Proc, mientras que los métodos son de tipo Method.
Michael Dorst
1
las variables globales son malas
achempion
@achempion, ¿dónde los ves?
Nakilon
@Nakilon mis disculpas, edite su respuesta si quiere que cancele mi voto
achempion
0

Hacer un módulo o clase privados

Las constantes nunca son privadas. Sin embargo, es posible crear un módulo o una clase sin asignarlo a una constante.

Entonces, una alternativa :private_class_methodes crear un módulo o clase privados y definir métodos públicos en él.

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

Uso:

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

Consulte los documentos de Module.new y Class.new .

Nathan Long
fuente
Me gusta mucho este método. Pero no parece posible eliminar el .selfen las definiciones del método, incluirlo en otra clase y usarlos como método_instancia de la clase incluida. ¿Sabes si hay alguna forma de hacerlo funcionar?
Shiyason
0

Este método no permitirá compartir datos con los métodos privados a menos que pase explícitamente los datos por los parámetros del método.

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
fuente