tags:

views:

108

answers:

3

Is there any way to override the setting of instance variables in Ruby?

Lets say I set an instance variable:

@foo = "bar"

Can I intercept that and do something (like record it or puts, for instance)

I suppose, I am trying to override the assignment operator for all types. Can this even be done?

The best I have come up with, so far, is this:

class Module
  def attr_log_accessor( *symbols )
    symbols.each { | symbol |
      module_eval( "def #{symbol}() @#{symbol}; end" )
      module_eval( "def #{symbol}=(val) 
                      @#{symbol} = val
                      puts \"#{symbol} has changed\"
                    end" )
    }
  end
end

Then, when I define the accessor and set it, my code gets executed:

class Test
  attr_log_accessor :foo

  def DoSomething
    self.foo = "bar"
  end
end

Unfortunately, this requires me to write self.foo = "bar", instead of @foo = "bar".

Any thoughts?

+1  A: 

No you can't do this directly. The best approach to this would be to only access the instance variables through getter/setter methods and override those.

ryeguy
+1  A: 

You could mess around with instance_variable_get, instance_variable_set and instance_variables. More can be found in this Ruby's Metaprogramming Toolbox article.

If you could describe why you're trying to do this we might be able to offer a better method.

rspeicher
I am trying to do this, because for specific properties on the class, I want to notify other objects when the properties change. More specifically, this is for integrating with .Net. I am using IronRuby and firing events on the INotifyPropertyChanged.PropertyChanged event.I'd like to declare a property as notifiable: attr_notifiable :foo, or something and whenever @foo is set, it fires the event. Ultimately, self.foo will work, but if I can get in at the ground floor (assignment to local var), then it will be even more useful to me.
Brian Genisio
A: 

You can do this using method_missing()

example:

def method_missing(name, *args)
  attribute = name.to_s
  if attribute =~ /=$/
    @attributes[attribute.chop] = args[0]
  else
    @attributes[attribute]
  end
end

If you have processing in your getter/setter, this method would quickly become bloated.

For more information, check out the OpenStruct source. Also, I pulled this code from Metaprogramming Ruby (pg 52)

Also, note that this will catch all missing methods. If you only want to intercept obj.[*]= then, you'll have to change @attributes[attribute] to super

Jim Schubert