如何使用类似宏的元编程方法扩展Ruby模块?

Ian*_*ell 6 ruby module metaprogramming

考虑以下扩展(多年来由多个Rails插件推广的模式):

module Extension
  def self.included(recipient)
    recipient.extend ClassMethods
    recipient.send :include, InstanceMethods
  end

  module ClassMethods
    def macro_method
      puts "Called macro_method within #{self.name}"
    end
  end

  module InstanceMethods
    def instance_method
      puts "Called instance_method within #{self.object_id}"
    end
  end
end
Run Code Online (Sandbox Code Playgroud)

如果您希望将其公开给每个班级,您可以执行以下操作:

Object.send :include, Extension
Run Code Online (Sandbox Code Playgroud)

现在您可以定义任何类并使用宏方法:

class FooClass
  macro_method
end
#=> Called macro_method within FooClass
Run Code Online (Sandbox Code Playgroud)

实例可以使用实例方法:

FooClass.new.instance_method
#=> Called instance_method within 2148182320
Run Code Online (Sandbox Code Playgroud)

但即使如此Module.is_a?(Object),您也无法在模块中使用宏方法.

module FooModule
  macro_method
end
#=> undefined local variable or method `macro_method' for FooModule:Module (NameError)
Run Code Online (Sandbox Code Playgroud)

这是真实的,即使你明确地包括原ExtensionModuleModule.send(:include, Extension).

对于单个模块,您可以手动包含扩展并获得相同的效果:

module FooModule
  include Extension
  macro_method
end
#=> Called macro_method within FooModule
Run Code Online (Sandbox Code Playgroud)

但是,如何为所有Ruby模块添加类似宏的方法呢?

Jör*_*tag 22

考虑以下扩展(多年来由多个Rails插件推广的模式)

不是一种模式,并没有"普及".这是一个反模式,由不知道Ruby的1337 PHP h4X0rZ 进行了货物测试.值得庆幸的是,由于Yehuda Katz,Carl Lerche和其他人的硬话,Rails 3已经消除了许多(所有?)这种反模式的实例.Yehuda甚至在他最近关于清理Rails代码库的讨论中使用了与你发布的完全相同的代码,并且他写了一篇关于这个反模式的完整博客文章.

如果您希望将其公开给每个班级,您可以执行以下操作:

Object.send :include, Extension
Run Code Online (Sandbox Code Playgroud)

如果你想将它添加到Object 任何地方,那么为什么不这样做呢:

class Object
  def instance_method
    puts "Called instance_method within #{inspect}"
  end
end
Run Code Online (Sandbox Code Playgroud)

但是,如何为所有Ruby模块添加类似宏的方法呢?

简单:将它们添加到Module:

class Module
  def macro_method
    puts "Called macro_method within #{inspect}"
  end
end
Run Code Online (Sandbox Code Playgroud)

一切正常:

class FooClass
  macro_method
end
#=> Called macro_method within FooClass

FooClass.new.instance_method
#=> Called instance_method within #<FooClass:0x192abe0>

module FooModule
  macro_method
end
#=> Called macro_method within FooModule
Run Code Online (Sandbox Code Playgroud)

它只有10行代码而不是你的16行,而这10行中恰好有0行是元编程或钩子或任何甚至远程复杂的东西.

代码与我的代码之间的唯一区别在于,在代码中,mixins显示在继承层次结构中,因此调试起来更容易,因为您实际上看到了添加的内容Object.但这很容易解决:

module ObjectExtensions
  def instance_method
    puts "Called instance_method within #{inspect}"
  end
end

class Object
  include ObjectExtensions
end

module ModuleExtensions
  def macro_method
    puts "Called macro_method within #{inspect}"
  end
end

class Module
  include ModuleExtensions
end
Run Code Online (Sandbox Code Playgroud)

现在我用你的代码绑定了16行,但我认为我的代码比你的简单,特别是考虑到你的代码不起作用,你和我以及几乎190000 StackOverflow用户都无法弄清楚原因.