背景:

我有一个模块,它声明了许多实例方法

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

  def format_text(x); ...
end

我想在类中调用其中一些方法。在ruby中你通常是这样做的:

class UsefulWorker
  include UsefulThings

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

问题

include UsefulThings引入了UsefulThings中的所有方法。在这种情况下,我只需要format_text和显式不需要get_file和delete_file。

我可以看到几个可能的解决方案:

Somehow invoke the method directly on the module without including it anywhere I don't know how/if this can be done. (Hence this question) Somehow include Usefulthings and only bring in some of it's methods I also don't know how/if this can be done Create a proxy class, include UsefulThings in that, then delegate format_text to that proxy instance This would work, but anonymous proxy classes are a hack. Yuck. Split up the module into 2 or more smaller modules This would also work, and is probably the best solution I can think of, but I'd prefer to avoid it as I'd end up with a proliferation of dozens and dozens of modules - managing this would be burdensome

为什么一个模块中有很多不相关的功能?它是来自rails应用程序的ApplicationHelper,我们的团队实际上已经决定将它作为任何不够特定而不属于其他任何地方的东西的垃圾场。大多数是独立的实用程序方法,在任何地方都可以使用。我可以把它分解成独立的助手,但有30个,每个都有一个方法……这似乎没有什么效果


当前回答

调用模块实例方法时不包含模块(也不创建中间对象):

class UsefulWorker
  def do_work
    UsefulThings.instance_method(:format_text).bind(self).call("abc")
    ...
  end
end

其他回答

调用模块实例方法时不包含模块(也不创建中间对象):

class UsefulWorker
  def do_work
    UsefulThings.instance_method(:format_text).bind(self).call("abc")
    ...
  end
end

如果你“拥有”这个模块,另一种方法是使用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

根据我对这个问题的理解,您希望将模块的一些实例方法混合到一个类中。

让我们首先考虑Module#include是如何工作的。假设我们有一个UsefulThings模块,它包含两个实例方法:

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

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

和Fixnum包括该模块:

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

我们看到:

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

您是否期望UsefulThings#add3覆盖Fixnum#add3,以便1。Add3返回4?考虑一下:

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

当类包含模块时,模块就成为类的超类。因此,由于继承的工作方式,将add3发送给Fixnum的实例将导致Fixnum#add3被调用,返回dog。

现在让我们添加一个方法:add2到UsefulThings:

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

我们现在希望Fixnum只包含add1和add3方法。这样做,我们期望得到与上面相同的结果。

假设,如上所述,我们执行:

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

结果是什么?不需要的方法:add2被添加到Fixnum,:add1被添加,由于上面解释的原因,:add3没有被添加。我们要做的就是undef:add2。我们可以用一个简单的helper方法来做到这一点:

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

我们这样调用:

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

然后:

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

这就是我们想要的结果。

9年之后,这里有一个通用的解决方案:

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

不幸的是,您需要应用的技巧是在定义方法之后才包含模块。或者,您还可以在上下文定义为ModuleIncluded之后将其包含进来。发送(:包括,CreateModuleFunctions)。

或者您可以通过reflection_utils gem使用它。

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

require 'reflection_utils'
include ReflectionUtils::CreateModuleFunctions

不确定10年后是否有人还需要它,但我用特征类解决了它。

module UsefulThings
  def useful_thing_1
    "thing_1"
  end

  class << self
    include UsefulThings
  end
end

class A
  include UsefulThings
end

class B
  extend UsefulThings
end

UsefulThings.useful_thing_1 # => "thing_1"
A.new.useful_thing_1 # => "thing_1"
B.useful_thing_1 # => "thing_1"