attr_history, almost perfect
Posted by Rob, Thu May 24 14:01:00 UTC 2007
I wanted to have an attr_history method that would behave like attr_accessor :foo but also keep a foo_history variable around so I can look back through the values the variable held during it’s life time.
Googling around and some help from Chris McGrath led me to write this:
module AttrHistory
def attr_history(*syms)
syms.each { |sym|
class_eval(<<-EOS, __FILE__, __LINE__)
attr_reader :#{sym}
attr_reader :#{sym}_history
def #{sym}=(value)
@#{sym}_history ||= []
if @#{sym}
@#{sym}_history << @#{sym}.dup
@#{sym}.replace(value)
else
@#{sym} = value
end
end
def has_#{sym}_history?
not @#{sym}_history.empty?
end
EOS
}
end
end
I was really pleased with the fact it worked, even that it was possible. I love the the shiney things you can do with *_eval and so on. There is just one problem, in that I can’t get it to behave quite like attr_accessor does. Because of the way Ruby parses code (explained at: http://www.rubycentral.com/book/language.html#UO) I end up having to use self.foo= when referring to the setter, because otherwise ruby thinks it’s a local variable. That’s a real shame in this instance, as I actually want the stuff to be available in an “DSL”-like environment similar to RSpec. As it is, the user must prefix the setters, self.foo= instead of simply foo=, which is a bit ugly. Oh well.
klass :name => /^Xsd(AnyUri|Byte|HexBinary|String|Xmltext)$/ do
self.name = 'String'
end
The ‘self.’ really bugs me. If anyone knows how to fix that, please let me know :)
Chris made a good suggestion that I just pass in a variable ‘k’ to the block so they can do k.name = which would resolve properly and not be so ugly. I think I’ll go with that for now, but I’d love to properly “fix it”. Not sure it’s possible though.
