我有几个类,每个类都定义了各种统计信息。
class MonthlyStat
attr_accessor :cost, :size_in_meters
end
class DailyStat
attr_accessor :cost, :weight
end
我想为这些对象的集合创建一个装饰器/演示者,这样我就可以轻松访问有关每个集合的聚合信息,例如:
class YearDecorator
attr_accessor :objs
def self.[]= *objs
new objs
end
def initialize objs
self.objs = objs
define_helpers
end
def define_helpers
if o=objs.first # assume all objects are the same
o.instance_methods.each do |method_name|
# sums :cost, :size_in_meters, :weight etc
define_method "yearly_#{method_name}_sum" do
objs.inject(0){|o,sum| sum += o.send(method_name)}
end
end
end
end
end
YearDecorator[mstat1, mstat2].yearly_cost_sum
不幸的是,在实例方法中无法使用define方法。
替换为:
class << self
define_method "yearly_#{method_name}_sum" do
objs.inject(0){|o,sum| sum += o.send(method_name)}
end
end
...也失败了,因为实例中定义的变量method_name和objs不再可用。在红宝石中有没有一个人可以做到这一点?
答案 0 :(得分:1)
(编辑:我得到你现在要做的事。)
好吧,我尝试了你可能采用的相同方法,但最终不得不使用eval
class Foo
METHOD_NAMES = [:foo]
def def_foo
METHOD_NAMES.each { |method_name|
eval <<-EOF
def self.#{method_name}
\"#{method_name}\".capitalize
end
EOF
}
end
end
foo=Foo.new
foo.def_foo
p foo.foo # => "Foo"
f2 = Foo.new
p f2.foo # => "undefined method 'foo'..."
我自己会承认这不是最优雅的解决方案(甚至可能不是最惯用的解决方案),但我在过去遇到类似的情况,其中最直接的方法是eval
。
答案 1 :(得分:0)
我很好奇你为o.instance_methods
得到了什么。这是一个类级方法,通常不能在对象实例上使用,从我所知道的,这就是你在这里处理的内容。
无论如何,您可能正在寻找method_missing
,它会在您第一次调用它时动态定义该方法,并允许您将:define_method
发送到对象的类。每次实例化一个新对象时都不需要重新定义相同的实例方法,因此method_missing
只有在尚未定义被调用方法时才允许您在运行时更改类。
由于您期望某个模式所包含的其他类中的方法名称(即yearly_base_sum
将对应于base
方法),我建议您编写一个返回的方法匹配模式,如果找到一个。注意:这不会涉及在另一个类上创建方法列表 - 如果您的某个对象不知道如何响应您发送的消息,则仍应依赖内置NoMethodError
。这样可以使您的API更加灵活,并且在您的统计信息类也可能在运行时进行修改时非常有用。
def method_missing(name, *args, &block)
method_name = matching_method_name(name)
if method_name
self.class.send :define_method, name do |*args|
objs.inject(0) {|obj, sum| sum + obj.send(method_name)}
end
send name, *args, &block
else
super(name, *args, &block)
end
end
def matching_method_name(name)
# ... this part's up to you
end