仅扩展一个块调用

时间:2009-06-09 21:31:47

标签: ruby module metaprogramming extend

我有一个包含一些私有属性的类。我想要做的是为这些只为执行特定块动态添加一些setter。

我希望能够做到的例子:

class Content
  attr_reader :a, :b

  def initialize
    @a = 1
    @b = "plop"
  end

  def set(&block)
    extend(Setter)
    instance_eval(&block)
    unextend(Setter) ????
  end

  module Setter
    def a(value)
      @a = value
    end
    def b(value)
      @b = value
    end
  end
end

content = Content.new
content.set do
  a 2
  b "yeah!"
end
content.a # should return 2

编辑:感谢目前为止的精彩答案。我澄清了这个问题,因为我实际上需要在类本身中定义可能与模块中定义的setter冲突的属性读取器。在发布问题时我忘记了这一部分。 (已晚了^^)

CLARIFICATION :此类用于DSL编写配置文件。它针对的是非开发人员,因此运营商越少越好。

我目前使用instance_eval块的代理类来实现它,但我必须弄乱instance_variable_set才能设置值而我不喜欢它。我只是想尝试另一种方法,看看我是否可以让我的代码更具可读性。

4 个答案:

答案 0 :(得分:3)

在Ruby中没有本地方法来“扩展”模块。 mixology gem将此模式实现为C(和Java,用于JRuby)扩展,创建mixinunmix方法。如果您需要Ruby 1.9支持,似乎您可能需要应用patch

如果您更愿意避免使用第三方库,另一种方法可能只是让setter私有化:

class Content
  def initialize
    @a = 1
    @b = "plop"
  end

  def set(&block)
    instance_eval(&block)
  end

  private

  def a(val)
    @a = val
  end

  def b(val)
    @b = val
  end 

end

content = Content.new

#This will succeed
content.set do
  a 2
  b "yeah!"
end

# This will raise a NoMethodError, as it attempts to call a private method
content.a 3

答案 1 :(得分:2)

  def set(&block)
    extend(Setter)
    instance_eval(&block)
    Setter.instance_methods.each do |m| 
      instance_eval "undef #{m}"
    end
  end

我不知道有什么方法能为你做到这一点虽然可能有些东西..这应该可以完成工作,通过查找Setter的所有实例方法并在Content中取消定义它们。

答案 2 :(得分:2)

您可以使用_why's mixico library(可用on github

它可以让你这样做:

require 'mixology'
#...
def set(&block)
  Setter.mix_eval(Setter, &block)
end

mixology gem做了很多相同的事情,只是略有不同。

答案 3 :(得分:0)

如果您感觉有实验情绪,请查看:dup_eval

它在某些方面类似于mixico但有一些有趣的附加功能(object2module