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

时间:2010-06-10 22:16:07

标签: ruby metaprogramming module

考虑以下扩展(多年来由多个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

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

Object.send :include, Extension

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

class FooClass
  macro_method
end
#=> Called macro_method within FooClass

实例可以使用实例方法:

FooClass.new.instance_method
#=> Called instance_method within 2148182320

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

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

即使您明确将原始Extension包含在Module Module.send(:include, Extension)中也是如此。

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

module FooModule
  include Extension
  macro_method
end
#=> Called macro_method within FooModule

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

1 个答案:

答案 0 :(得分:22)

  

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

这是不是模式,并没有“普及”。这是一个反模式,由1337 PHP h4X0rZ goods-culted ,他们不懂Ruby。值得庆幸的是,由于Yehuda Katz,Carl Lerche和其他人的硬话,Rails 3已经消除了许多(所有?)这种反模式的实例。在他最近关于清理Rails代码库的讨论中,Yehuda甚至使用了与你发布的完全相同的代码作为反例,他写了an entire blog post这个反模式。

  

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

Object.send :include, Extension

如果您想将其添加到Object ,那么为什么不这样做呢?

class Object
  def instance_method
    puts "Called instance_method within #{inspect}"
  end
end
  

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

简单:将它们添加到Module

class Module
  def macro_method
    puts "Called macro_method within #{inspect}"
  end
end

一切正常:

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

只有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

现在我与16行的代码绑在一起,但我认为我的代码比你的简单,特别是考虑到你的代码不起作用,你和我,以及差不多190000的StackOverflow用户都无法找出原因。