这里有一个简单的例子:
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。
答案 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
的所有子类中,在我的示例中,A
为Class
。
答案 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
如果他们出现,我会接受更好的答案。