从模块创建类方法

时间:2012-10-09 02:23:20

标签: ruby metaprogramming

这里有一个简单的例子:

class Base
  @tag = nil 

  def self.tag(v = nil) 
    return @tag unless v 
    @tag = v
  end 
end 

class A < Base 
  tag :A
end

class B < Base
  tag :B
end 

class C < Base; end

puts "A: #{A.tag}"
puts "B: #{B.tag}"
puts "A: #{A.tag}"
puts "C: #{C.tag}"

按预期工作

A: A
B: B 
A: A
C: 

我想创建一个模块,base将扩展为提供相同的功能,但包含类指定的所有标记信息。例如。

module Tester 
  def add_ident(v); ....; end
end

class Base 
  extend Tester 

  add_ident :tag
end 

我发现我可以直接评估,所以:

def add_ident(v)
  v = v.to_s 
  eval "def self.#{v}(t = nil); return @#{v} unless t; @#{v} = t; end"
end

但我真的不喜欢在任何语言中使用eval字符串。

有没有办法可以在不使用eval的情况下获得此功能?我已经完成了我能想到的define_method和instance_variable_get / set的每个组合,我无法让它工作。

没有Rails的Ruby 1.9。

4 个答案:

答案 0 :(得分:3)

您希望在要扩展的类的单例类上定义动态方法。可以使用如下表达式访问类的单例类:class << self; self end。要打开类的范围,可以使用class_eval。把所有这些放在一起,你可以写:

module Identification

  def add_identifier(identifier)
    (class << self; self end).class_eval do
      define_method(identifier) do |*args|
        value = args.first
        if value
          instance_variable_set("@#{identifier}", value)
        else
          instance_variable_get("@#{identifier}")
        end
      end
    end
  end

end

class A
  extend Identification

  add_identifier :tag
end

如果您使用的是最新版本的Ruby,则此方法可以替换为Module#define_singleton_method

module Identification

  def add_identifier(identifier)
    define_singleton_method(identifier) do |value = nil|
      if value
        instance_variable_set("@#{identifier}", value)
      else
        instance_variable_get("@#{identifier}")
      end
    end
  end

end

我不相信你想使用self.class.send(:define_method),如另一个答案所示;这会产生意外的副作用,即将动态方法添加到self.class的所有子类中,在我的示例中,AClass

答案 1 :(得分:1)

module Tester
  def add_ident(var)
    self.class.send(:define_method, var) do |val=nil|
        return instance_variable_get("@#{var}") unless val
        instance_variable_set "@#{var}", val
      end
    end
end

答案 2 :(得分:1)

我最喜欢的红宝石书Metaprogramming Ruby解决了这些问题,如下所示:

module AddIdent 
  def self.included(base)
    base.extend ClassMethods    # hook method
  end

  module ClassMethods
    def add_ident(tag)
      define_method "#{tag}=" do |value=nil|
        instance_variable_set("@#{tag}", value)
      end

      define_method tag do 
        instance_variable_get "@#{tag}"
      end 
    end
  end
end 

# And use it like this
class Base
  include AddIdent

  add_ident :tag
end

答案 3 :(得分:0)

一旦你沮丧得足以发布你然后找到答案,那么总是不是这样的方式:)

诀窍似乎是在(class&lt;&lt; self; self; end)中为您提供类实例而不破坏本地范围。引用:How do I use define_method to create class methods?

def add_ident(v) 
  var_name = ('@' + v.to_s).to_sym 
  (class << self; self; end).send(:define_method, v) do |t = nil|
    return instance_variable_get(var_name) unless t
    instance_variable_set(var_name, t)
  end 
end 

如果他们出现,我会接受更好的答案。