modules - Posso invocar um método de instância em um módulo Ruby sem incluí-lo?




ruby:: module (6)

A. Caso você, sempre queira chamá-los de uma maneira "qualificada", independente (UsefulThings.get_file), então faça-os estáticos como outros apontaram,

module UsefulThings
  def self.get_file; ...
  def self.delete_file; ...

  def self.format_text(x); ...

  # Or.. make all of the "static"
  class << self
     def write_file; ...
     def commit_file; ...
  end

end

B. Se você ainda quiser manter a abordagem mixin nos mesmos casos, bem como a invocação individual isolada, você pode ter um módulo de uma linha que se estende com o mixin:

module UsefulThingsMixin
  def get_file; ...
  def delete_file; ...

  def format_text(x); ...
end

module UsefulThings
  extend UsefulThingsMixin
end

Então, ambos funcionam então:

  UsefulThings.get_file()       # one off

   class MyUser
      include UsefulThingsMixin  
      def f
         format_text             # all useful things available directly
      end
   end 

IMHO é mais limpo que module_function para cada método - no caso de querer todos eles.

Fundo:

Eu tenho um módulo que declara vários métodos de instância

module UsefulThings
  def get_file; ...
  def delete_file; ...

  def format_text(x); ...
end

E eu quero chamar alguns desses métodos de dentro de uma classe. Como você normalmente faz isso em ruby ​​é assim:

class UsefulWorker
  include UsefulThings

  def do_work
    format_text("abc")
    ...
  end
end

Problema

include UsefulThings traz todos os métodos de UsefulThings . Neste caso eu só quero format_text e explicitamente não quero get_file e delete_file .

Eu posso ver várias soluções possíveis para isso:

  1. De alguma forma invocar o método diretamente no módulo sem incluí-lo em qualquer lugar
    • Eu não sei como / se isso pode ser feito. (Daí esta questão)
  2. De alguma forma incluem Usefulthings e só traz alguns dos seus métodos
    • Eu também não sei como / se isso pode ser feito
  3. Crie uma classe de proxy, inclua UsefulThings em seguida, delegue format_text para essa instância do proxy
    • Isso funcionaria, mas as classes proxy anônimas são um hack. Que nojo.
  4. Dividir o módulo em 2 ou mais módulos menores
    • Isso também funcionaria, e é provavelmente a melhor solução que posso imaginar, mas eu preferiria evitá-lo, pois acabaria com uma proliferação de dúzias e dúzias de módulos - gerenciar isso seria uma tarefa difícil.

Por que existem muitas funções não relacionadas em um único módulo? É o ApplicationHelper de um aplicativo de trilhos, que nossa equipe decidiu de fato como o depósito para qualquer coisa que não seja específica o suficiente para pertencer a qualquer outro lugar. Principalmente métodos de utilitário autônomos que são usados ​​em todos os lugares. Eu poderia dividir em ajudantes separados, mas haveria 30 deles, todos com 1 método cada ... isso parece improdutivo


Depois de quase 9 anos, aqui está uma solução genérica:

module CreateModuleFunctions
  def self.included(base)
    base.instance_methods.each do |method|
      base.module_eval do
        module_function(method)
        public(method)
      end
    end
  end
end

RSpec.describe CreateModuleFunctions do
  context "when included into a Module" do
    it "makes the Module's methods invokable via the Module" do
      module ModuleIncluded
        def instance_method_1;end
        def instance_method_2;end

        include CreateModuleFunctions
      end

      expect { ModuleIncluded.instance_method_1 }.to_not raise_error
    end
  end
end

O truque infeliz que você precisa aplicar é incluir o módulo após os métodos terem sido definidos. Alternativamente, você também pode incluí-lo após o contexto ser definido como ModuleIncluded.send(:include, CreateModuleFunctions) .

Ou você pode usá-lo através da gem reflection_utils .

spec.add_dependency "reflection_utils", ">= 0.3.0"

require 'reflection_utils'
include ReflectionUtils::CreateModuleFunctions

Eu acho que o caminho mais curto para fazer uma chamada simples (sem alterar os módulos existentes ou criar novos) seria o seguinte:

Class.new.extend(UsefulThings).get_file

Outra maneira de fazer isso se você "possui" o módulo é usar module_function .

module UsefulThings
  def a
    puts "aaay"
  end
  module_function :a

  def b
    puts "beee"
  end
end

def test
  UsefulThings.a
  UsefulThings.b # Fails!  Not a module method
end

test

Pelo que entendi, você quer misturar alguns dos métodos de instância de um módulo em uma classe.

Vamos começar considerando como o Module#include trabalhos. Suponha que tenhamos um módulo UsefulThings que contém dois métodos de instância:

module UsefulThings
  def add1
    self + 1
  end
  def add3
    self + 3
  end
end

UsefulThings.instance_methods
  #=> [:add1, :add3]

e Fixnum include s esse módulo:

class Fixnum
  def add2
    puts "cat"
  end
  def add3
    puts "dog"
  end
  include UsefulThings
end

Nós vemos que:

Fixnum.instance_methods.select { |m| m.to_s.start_with? "add" }
  #=> [:add2, :add3, :add1] 
1.add1
2 
1.add2
cat
1.add3
dog

Você esperava que UsefulThings#add3 substituísse Fixnum#add3 , de modo que 1.add3 retornasse 4 ? Considere isto:

Fixnum.ancestors
  #=> [Fixnum, UsefulThings, Integer, Numeric, Comparable,
  #    Object, Kernel, BasicObject] 

Quando a classe include o módulo, o módulo se torna a superclasse da classe. Então, por causa de como a herança funciona, enviar o add3 para uma instância do Fixnum fará com que o Fixnum#add3 seja invocado, retornando o dog .

Agora vamos adicionar um método :add2 para UsefulThings :

module UsefulThings
  def add1
    self + 1
  end
  def add2
    self + 2
  end
  def add3
    self + 3
  end
end

Agora desejamos que o Fixnum include apenas os métodos add1 e add3 . Assim, esperamos obter os mesmos resultados acima.

Suponha, como acima, que executemos:

class Fixnum
  def add2
    puts "cat"
  end
  def add3
    puts "dog"
  end
  include UsefulThings
end

Qual é o resultado? O método indesejado :add2 é adicionado ao Fixnum :add1 é adicionado e, por razões explicadas acima :add3 não é adicionado. Então tudo o que temos a fazer é undef :add2 . Podemos fazer isso com um método auxiliar simples:

module Helpers
  def self.include_some(mod, klass, *args)
    klass.send(:include, mod)
    (mod.instance_methods - args - klass.instance_methods).each do |m|
      klass.send(:undef_method, m)
    end
  end
end

que nós invocamos assim:

class Fixnum
  def add2
    puts "cat"
  end
  def add3
    puts "dog"
  end
  Helpers.include_some(UsefulThings, self, :add1, :add3)
end

Então:

Fixnum.instance_methods.select { |m| m.to_s.start_with? "add" }
  #=> [:add2, :add3, :add1] 
1.add1
2 
1.add2
cat
1.add3
dog

qual é o resultado que queremos.


Se um método em um módulo for transformado em uma função de módulo, você pode simplesmente chamá-lo de Mods como se ele tivesse sido declarado como

module Mods
  def self.foo
     puts "Mods.foo(self)"
  end
end

A abordagem module_function abaixo evitará quebrar qualquer classe que inclua todos os Mods.

module Mods
  def foo
    puts "Mods.foo"
  end
end

class Includer
  include Mods
end

Includer.new.foo

Mods.module_eval do
  module_function(:foo)
  public :foo
end

Includer.new.foo # this would break without public :foo above

class Thing
  def bar
    Mods.foo
  end
end

Thing.new.bar  

No entanto, estou curioso para saber por que um conjunto de funções não relacionadas está contido no mesmo módulo em primeiro lugar?

Editado para mostrar que inclui ainda funciona se public :foo é chamado após module_function :foo





methods