我正在参加SaaS Stanford课程,试图完成this assignment
的第5部分我很难掌握这个概念,这是我试图做的事情:
class Class
def attr_accessor_with_history(attr_name)
attr_name = attr_name.to_s
attr_reader attr_name
attr_reader attr_name + '_history'
class_eval %Q'{def #{attr_name}(a);#{attr_name}_history.push(a) ; end;}'
end
end
我可能做错了各种各样的事情,阅读关于元编程的The Book of Ruby章节,我仍然没有得到它,有人能帮助我理解这个吗?
答案 0 :(得分:41)
这很有趣!!!
class Class
def attr_accessor_with_history(attr_name)
attr_name = attr_name.to_s # make sure it's a string
attr_reader attr_name
attr_reader attr_name+"_history"
class_eval %Q"
def #{attr_name}=(value)
if !defined? @#{attr_name}_history
@#{attr_name}_history = [@#{attr_name}]
end
@#{attr_name} = value
@#{attr_name}_history << value
end
"
end
end
class Foo
attr_accessor_with_history :bar
end
class Foo2
attr_accessor_with_history :bar
def initialize()
@bar = 'init'
end
end
f = Foo.new
f.bar = 1
f.bar = nil
f.bar = '2'
f.bar = [1,nil,'2',:three]
f.bar = :three
puts "First bar:", f.bar.inspect, f.bar_history.inspect
puts "Correct?", f.bar_history == [f.class.new.bar, 1, nil, '2', [1,nil,'2',:three], :three] ? "yes" : "no"
old_bar_history = f.bar_history.inspect
f2 = Foo2.new
f2.bar = 'baz'
f2.bar = f2
puts "\nSecond bar:", f2.bar.inspect, f2.bar_history.inspect
puts "Correct?", f2.bar_history == [f2.class.new.bar, 'baz', f2] ? "yes" : "no"
puts "\nIs the old f.bar intact?", f.bar_history.inspect == old_bar_history ? "yes" : "no"
请注意,您需要在class_eval中使用字符串的唯一原因是,在定义自定义setter时,您可以引用attr_name
的值。否则,通常会将一个块传递给class_eval
。
答案 1 :(得分:6)
关于你所做的事,你实际上是在解决方案的尖端。只是代码中不存在#{attr_name}_history
。您将需要创建一个实例变量,如果它不存在则将其设置为nil。如果确实存在,你已经应该处理进入数组的内容。
有几种方法可以做到这一点。一种方法是if defined? @#{attr_name}_history DoStuffHere
答案 2 :(得分:0)
你必须注意到#{attr_name}_history
是一个实例变量,所以在之前使用@,就像下面的类中的@foo一样
def #{attr_name}=value
,#{attr_name}=
是方法名称,value
是参数,与def func parameter
相同
def #{attr_name}=value
(!defined? @#{attr_name}_history) ? @#{attr_name}_history = [nil, value] : @#{attr_name}_history << value
end