Ruby:Mixin,它添加了动态实例方法,其名称是使用类方法创建的

时间:2011-04-06 21:39:42

标签: ruby-on-rails ruby module mixins

我有以下内容:

module Thing
  def self.included(base)
    base.send :extend, ClassMethods
  end

  module ClassMethods
    attr_reader :things

    def has_things(*args)
      options = args.extract_options! # Ruby on Rails: pops the last arg if it's a Hash

      # Get a list of the things (Symbols only)
      @things = args.select { |p| p.is_a?(Symbol) }

      include InstanceMethods
    end
  end

  module InstanceMethods
    self.class.things.each do |thing_name| # !!! Problem is here, explaination below
      define_method "foo_for_thing_#{thing_name}" do
        "bar for thing #{thing_name}"
      end
    end
  end
end

在另一个混合了Thing模块的类中:

class Group
  has_things :one, :two, :option => "something"
end

在类中调用has_things时,我希望动态的“foo_for_thing_one”和“foo_for_thing_two”实例方法可用。例如:

@group = Group.new
@group.foo_for_thing_one # => "bar for thing one"
@group.foo_for_thing_two # => "bar for thing two"

但是,我收到以下错误:

`<module:InstanceMethods>': undefined method `things' for Module:Class (NoMethodError)

我意识到上面指出的问题行中的“self”(InstanceMethods模块的第一行)引用了InstanceMethods模块。

如何引用“things”类方法(在此示例中返回[:one,:two]),以便我可以遍历并为每个方法创建动态实例方法?谢谢。或者如果您有其他建议来完成此任务,请告诉我。

1 个答案:

答案 0 :(得分:19)

快速回答:

将InstanceMethods的内容放在has_things方法定义中,然后删除InstanceMethods模块。

更好的答案:

您对InstanceMethods-ClassMethods反模式的使用在这里尤其没有根据,因此货物结果增加了您对范围和上下文的困惑。做最简单的事可能有用。不要在没有批判性思维的情况下复制别人的代码。

您需要的唯一模块是ClassMethods,它应该被赋予一个有用的名称,不应该被包含在内,而是用于扩展要授予has_things功能的类。这是最简单的事情:

module HasThings
  def has_things(*args)
    args.each do |thing|
      define_method "thing_#{thing}" do
        "this is thing #{thing}"
      end
    end
  end
end

class ThingWithThings
  extend HasThings
  has_things :foo
end

ThingWithThings.new.thing_foo # => "this is thing foo"

只在需要时添加复杂性(选项提取,输入规范化等)。代码及时,不仅仅是以防万一。