Métodos de módulo privados em Ruby

108

Eu tenho uma pergunta de duas partes

Melhor prática

  • Eu tenho um algoritmo que executa algumas operações em uma estrutura de dados usando a interface pública
  • Atualmente, é um módulo com vários métodos estáticos, todos privados, exceto o método de interface pública.
  • Existe uma variável de instância que precisa ser compartilhada entre todos os métodos.

Estas são as opções que consigo ver, qual é a melhor ?:

  • Módulo com métodos estáticos ('módulo' em ruby)
  • Classe com métodos estáticos
  • Módulo Mixin para inclusão na estrutura de dados
  • Refatore a parte do algoritmo que modifica essa estrutura de dados (muito pequena) e faça disso uma combinação que chama os métodos estáticos do módulo do algoritmo

Parte técnica

Existe alguma maneira de fazer um método de módulo privado ?

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

O privatein lá parece não ter nenhum efeito , ainda posso ligar Thing.privsem problemas.

Daniel Beardsley
fonte
5
Para sua informação, não existe método 'estático' no Ruby, eles são chamados de métodos de instância de classe
brad
31
Um comentário antigo, mas como tem quatro votos positivos, devo salientar que não existe um 'método de instância de classe'. 'Método de classe' é o termo correto.
micapam de
5
privateafeta apenas métodos de instância, não métodos de classe. use no private_class_methodlugar:module Thing; def self.pub; end; private_class_method :pub; end
apeiros
1
@micapam Métodos de instância de classe existem em Ruby e são diferentes dos métodos de classe.
Marnen Laibow-Koser

Respostas:

88

Acho que a melhor maneira (e principalmente como as bibliotecas existentes são escritas) de fazer isso é criando uma classe dentro do módulo que lida com toda a lógica e o módulo fornece apenas um método conveniente, por exemplo

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
fonte
14
Ruby newb aqui. Neste exemplo, a classe Translator é exposta como parte da interface pública do módulo? O método 'perform' pode ter seu acesso restrito ao GTranslate?
rshepherd
2
@rshepherd O performnão é o método que supostamente é privado aqui, o método privado é o método privado da Translatorclasse (o exemplo de @ ucron não tem nenhum, o que é muito lamentável). GTranslate.translateé apenas um método conveniente, pois GTranslate::Translator#performnão há ganho real em ocultá-lo, se é que era possível.
michelpm
28
Não tenho certeza do que é alcançado por ter uma aula aqui. Se o objetivo é ter um método de módulo privado, isso não atende ao objetivo. Porque você pode acessar o método "executar" de fora do módulo, chamando GTranslate :: Translator.new.perform. Em outras palavras, não é privado.
Zack Xu
1
@jschorr Acho que o Op e esta resposta pretendem fazer uma classe privada ou método de módulo, não um método de instância. Além disso, isso não tornará nenhum método de instância privado, pois self.translatedeclara um método de classe / módulo.
konsolebox
5
GTranslate::Translator.new.perform(text)- complicado, mas não privado!
abhillman
80

Há também Module.private_class_method, o que provavelmente expressa mais intenção.

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

Para o código em questão:

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 mais recente:

module Thing
  def self.pub; puts "Public method"; end
  private_class_method def self.priv; puts "Private method"; end
end
konsolebox
fonte
Eu não estava ciente disso. Vai funcionar antes da definição do método também private?
Marnen Laibow-Koser,
7
Esta resposta, junto com a resposta de @JCooper, é a solução real. @MarnenLaibow-Koser Não. Você pode considerar a outra resposta ao custo de mais agrupamentos e recuos. Na verdade, pode ser a solução preferida para alguns. (Respondendo apenas para referência.)
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
fonte
4
Esta deve ser a resposta aceita. Limpo e idiomático na minha opinião.
Martin Nyaga
@MartinNyaga isso tem a desvantagem de não ter include Writeropção!
Ulysse BN
28

Você pode usar o método "incluído" para fazer coisas fantasiosas quando um módulo é misturado. Isso faz o que você quiser, eu acho:

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
Cameron Price
fonte
5
Isso é inteligente. Portanto, é possível, mas provavelmente não vale a pena.
Daniel Beardsley
funciona bem. Usei em included do |base| [...] endvez de def
Crystark 01 de
5
@Crystark: Essa sintaxe só existe em módulos que estendem ActiveSupport :: Concern, se não me engano. ou seja, é uma coisa de trilhos.
Vaz
11

Infelizmente, privatesó se aplica a métodos de instância. A maneira geral de obter métodos "estáticos" privados em uma classe é fazer algo como:

class << self
  private

  def foo()
   ....
  end
end

Admito que não brinquei com isso em módulos.

J Cooper
fonte
7
Isso não é verdade. Você pode ter métodos de classe privada e métodos de módulo privado.
mikeycgto de
Você pode ter métodos de classe privados, mas apenas fazer isso não tornará .fooum método de classe privado: "private; def self.foo ()"
Ari
@mikeycgto Se preocupa em elaborar a diferença entre métodos de classe privada e métodos de módulo privado? Porque eu acho que eles são iguais. Note-se que tanto privatee private_class_methodsão de propriedade Modulenão Class. Este código funciona por sinal e é a alternativa de uso private_class_method.
konsolebox de
3

Uma maneira legal é assim

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

Que tal armazenar métodos como lambdas em variáveis ​​/ constantes de classe?

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

Para teste:
UPD: grande atualização deste código após 6 anos mostra uma maneira mais limpa de declarar o 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

Aqui 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) @@Lnão pode ser acessado de fora, mas é acessível de quase todos os lugares
2) class << self ; private ; deftorna o método dinacessível de fora e de dentro com sucesso, self.mas não sem ele - isso é estranho
3) private ; self.e private ; class << selfnão torna os métodos privados - eles são acessíveis ambos com e semself.

Nakilon
fonte
lambdas não são a mesma coisa que métodos. lambdas são do tipo Proc, enquanto os métodos são do tipo Method.
Michael Dorst
1
variáveis ​​globais são ruins
acúmulo de
@achempion, onde você os vê?
Nakilon,
@Nakilon minhas desculpas, edite sua resposta se você quiser que eu cancele meu voto
achempion
0

Faça um módulo ou classe privada

As constantes nunca são privadas. No entanto, é possível criar um módulo ou classe sem atribuí-lo a uma constante.

Portanto, uma alternativa :private_class_methodé criar um módulo ou classe privada e definir métodos públicos nele.

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 os documentos de Module.new e Class.new .

Nathan Long
fonte
Eu realmente gosto desse método. Mas não parece possível remover o .selfnas definições do método, incluí-lo em outra classe e usá-los como instance_methods da classe de inclusão. Você sabe se há alguma maneira de fazer funcionar?
Shiyason
0

Este método não permitirá o compartilhamento de dados com os métodos privados, a menos que você passe explicitamente os dados por parâmetros de 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
fonte