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.

The code for using Spex (which I’ll release soon, certainly before the next meeting) ends up looking like:

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.

Filed Under: | Tags:

Comments

  1. Olivier 05.25.07 / 08AM
    Rob, a neat way to remove the need for "self" is to yield "self" to the block. You use it like this:
    klass :name => regexp do |k|
      k.name = 'String'
    end