【问题标题】:Extending a Ruby module in another module, including the module methods在另一个模块中扩展 Ruby 模块,包括模块方法
【发布时间】:2011-01-11 21:12:17
【问题描述】:

每当我尝试扩展 ruby​​ 模块时,都会丢失模块方法。 include 和 extend 都不会这样做。考虑 sn-p:

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

module B 
  include A
end

B.say_hi  #undefined_method

无论 B 包含还是扩展 A,say_hi 都不会被定义。

有没有办法完成这样的事情?

【问题讨论】:

  • 您能提供一个具体案例来说明您希望这样做吗?模块继承是您真正想要的,并且这是专门且有意地仅支持 Ruby 中的类。为什么不是 a) 总是在同一个对象中包含 B 和 A,或者 b) 在包含 B 时也包含 A?
  • 我想要这个的原因有点复杂。有问题的模块用于从描述 DB 表和关系的 XML 文档创建大量 ActiveRecord::Base 类。我希望能够多次执行此操作。每个模块的代码都是相同的,但是为了避免命名空间和相关类发生冲突,它们需要是不同的模块。我也不想每次执行此操作时都必须实例化一个类来包含模块,排除实例方法。

标签: ruby module


【解决方案1】:

如果您是module 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 中。
  • @JonathanMartin 然后,您需要使用可重现的测试用例来更新您的问题。也许 ActiveRecord 正在做一些疯狂的魔法并向您的空间发送垃圾邮件,但它应该可以正常工作。
【解决方案2】:

我认为没有任何简单的方法可以做到这一点。

所以这里有一个复杂的方法:

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

【讨论】:

  • 太棒了!我花了很长时间才弄清楚如何解决这个错误:TypeError: singleton method called for a different object#to_proc 轻松解决了这个问题。
【解决方案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 ext 仅适用于 ruby​​ 1.9.2。 =)
  • @puchu 看看我留下这个答案的状态——2011
  • 2015 年 7 月 16 日。今天,您的 gem 仍然无法与 ruby​​ 2.2 和 2.3 一起使用。
  • @puchu 不,它说的是 2011 年 1 月 11 日,它今天不工作,因为 C API 和内部结构在 ruby​​ 版本之间发生了巨大变化,并且 gem 是 C 扩展 :)
【解决方案4】:

乔纳森, 我不确定您是否仍然对此感到疑惑,但是在 ruby​​ 中使用模块有两种不同的方法。 A.)您在代码中直接使用自包含形式 Base::Tree.entity(params) 的模块,或 B.)您将模块用作 mixins 或辅助方法。

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.混音 这些是你的朋友。每当您认为必须在代码中多次执行某项操作时,请使用它们。

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

现在,当您需要将 xmlstring 格式化为哈希,或删除任何未来代码中的尾随空格时,只需将其混入即可。

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 关键字,但是如果您想在对象中保持状态,只需使用类并混合 -在你想要的模块中。

【讨论】:

    【解决方案5】:

    我不喜欢每个人都使用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
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-02-24
      • 1970-01-01
      • 2013-09-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-04-17
      相关资源
      最近更新 更多