在另一个模块中扩展Ruby模块,包括该模块的方法。

35
无论我如何尝试扩展 Ruby 模块,都会失去模块方法。既不是 include 也不是 extend。考虑以下代码片段:
module A 
  def self.say_hi
    puts "hi"
  end
end

module B 
  include A
end

B.say_hi  #undefined_method
无论B是包含还是继承A,都不会定义say_hi。是否有任何方法可以实现这样的功能?

1
你能提供一个具体的案例,说明你为什么想要这样做吗?模块继承是你真正想要的,而这在 Ruby 中只支持类。为什么不要么a)总是将B和A包含在同一个对象中,要么b)在包含B时也包含A呢? - Phrogz
我想要这个的原因有点复杂。所涉及的模块用于从描述DB表和关系的XML文档创建许多ActiveRecord::Base类。我想能够多次执行此操作。每个操作的模块代码相同,但是为了避免命名空间和相关类冲突,它们需要是不同的模块。我也不想每次执行此操作时都实例化一个类来包含模块,这排除了实例方法。 - Jonathan Martin
5个回答

28

如果你是模块A的作者,并且经常需要这样做,你可以像这样重新编写A:

module A 
  module ClassMethods
    def say_hi
      puts "hi"
    end
  end
  extend ClassMethods
  def self.included( other )
    other.extend( ClassMethods )
  end
end

module B 
  include A
end

A.say_hi #=> "hi"
B.say_hi #=> "hi" 

我尝试过这个方法,但对于我的情况不起作用。正如我在评论中提到的(我想是在你的回答之后),这样做的目的是使用一个函数,在模块空间内使用const_set和Class.new创建类... 但是!如果我这样做并调用B.create_classes,我发现这些类不仅在B中被定义,而且在A中也被定义了。 - Jonathan Martin
1
@JonathanMartin 你需要更新你的问题并提供一个可重现的测试用例。也许ActiveRecord正在做一些疯狂的魔法并且在你的空间中垃圾邮件,但它应该可以正常工作。 - Phrogz

9
我认为没有简单的方法来做到这一点。因此,这里提供一种复杂的方法:

所以这是一个复杂的方法:

module B
  class << self
    A.singleton_methods.each do |m|
      define_method m, A.method(m).to_proc
    end
  end
end

你可以将它放入一个辅助方法中,如下所示:
class Module
  def include_module_methods(mod)
    mod.singleton_methods.each do |m|
      (class << self; self; end).send :define_method, m, mod.method(m).to_proc
    end
  end
end

module B
  include_module_methods A
end

1
太棒了!我花了很长时间才解决了这个错误:TypeError: singleton method called for a different object。使用 #to_proc 轻松解决了这个问题。 - Adam Eberlin

3

使用 include_complete 工具

gem install include_complete

module A 
  def self.say_hi
    puts "hi"
  end
end

module B 
  include_complete A
end

B.say_hi #=> "hi"

你说,你的 gem 的 C 扩展仅适用于 Ruby 1.9.2。=) - puchu
@puchu 看看我留下的答案状态 -- 2011 - horseyguy
2015年7月16日。你的宝石仍然无法与Ruby 2.2和2.3一起使用。 - puchu
@puchu 不是的,它说的是2011年1月11日,今天无法工作是因为Ruby版本之间的C API和内部结构发生了巨大变化,而这个gem是一个C扩展程序 :) - horseyguy

2

约翰,我不确定你是否还在思考这个问题,但在Ruby中有两种不同的使用模块的方式。A.) 直接在代码中以它们自包含的形式Base::Tree.entity(params)使用模块,或者B.) 将模块用作混合类或帮助方法。

A. 允许您将模块用作命名空间模式。这对于存在方法名称冲突的大型项目很有用。

module Base
  module Tree
    def self.entity(params={},&block)
      # some great code goes here
    end
  end
end

现在,您可以使用此功能在代码中创建某种树形结构,而无需为每次对Base :: Tree.entity的调用实例化新类。
另一种进行命名空间的方法是逐个类进行。
module Session
  module Live
    class Actor
      attr_accessor :type, :uuid, :name, :status
      def initialize(params={},&block)
        # check params, insert init values for vars..etc
        # save your callback as a class variable, and use it sometime later
        @block = block
      end

      def hit_rock_bottom
      end

      def has_hit_rock_bottom?
      end

      ...
    end
 end
 class Actor
   attr_accessor :id,:scope,:callback
   def initialize(params={},&block)
     self.callback = block if block_given?
   end

   def respond
     if self.callback.is_a? Proc
       # do some real crazy things...
     end
   end
 end
end

现在我们的类可能存在重叠。当我们创建一个Actor类时,我们需要确保它是正确的类,这就是命名空间派上用场的地方。

Session::Live::Actor.new(params) do |res|...
Session::Actor.new(params) 

B. 混入(Mix-Ins) 这些是你的好朋友。在你认为需要在代码中多次执行某个操作时使用它们。

module Friendly
  module Formatter
    def to_hash(xmlstring)
      #parsing methods
      return hash
    end

    def remove_trailing_whitespace(string,&block)
      # remove trailing white space from that idiot who pasted from textmate
    end
  end
end

现在,每当您需要将xml字符串格式化为哈希表,或在未来的任何代码中删除尾随空格时,只需混合使用即可。
module Fun
  class Ruby
    include Friendly::Formatter

    attr_accessor :string

    def initialize(params={})
    end

  end
end

现在您可以在您的类中格式化字符串。
fun_ruby = Fun::Ruby.new(params)
fun_ruby.string = "<xml><why><do>most</do><people></people><use>this</use><it>sucks</it></why></xml>"
fun_ruby_hash = fun_ruby.to_hash(fun_ruby.string)

希望这已经是足够好的解释了。上述提出的观点是扩展类的良好示例,但使用模块时,难点在于何时使用self关键字。它指的是 Ruby 对象层次结构中对象的范围。因此,如果您想将模块用作混合项,并且不想声明任何单例内容,请不要使用 self 关键字;然而,如果您想在对象内保留状态,请只使用类并混入所需的模块。

0

我不喜欢每个人都使用self.included。我的解决方案更简单:

module A
  module ClassMethods
    def a
      'a1'
    end
  end
  def a
    'a2'
  end
end

module B
  include A

  module ClassMethods
    include A::ClassMethods
    def b
      'b1'
    end
  end
  def b
    'b2'
  end
end

class C
  include B
  extend B::ClassMethods
end

class D < C; end

puts D.a
puts D.b
puts D.new.a
puts D.new.b

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接