Detecting when an object's instance variables are modified

Hi,

I have a whole load of objects which I would like to periodically dump
as YAML. However, in order to save on resources, I would prefer to only
dump those whose instance variables have changed since the last dump.

Is it possible to somehow 'mark' an object when the value any of its
instance variables is changed?

Thanks for any help :slight_smile:

路路路

--
Posted via http://www.ruby-forum.com/.

Paul Murton wrote:

I have a whole load of objects which I would like to periodically dump as YAML. However, in order to save on resources, I would prefer to only dump those whose instance variables have changed since the last dump.

Is it possible to somehow 'mark' an object when the value any of its instance variables is changed?

For the more "magical" way, you could play around with set_trace_func to monitor setters. Would probably be horribly, terribly slow.

It's probably best to have your objects trace changes by design. Modify the Object class to have a #dirty? and #clean! method, and alias / redefine the #attr_writer method to generate methods that will set the dirty flag when used?

David Vallner

Hi --

Hi,

I have a whole load of objects which I would like to periodically dump
as YAML. However, in order to save on resources, I would prefer to only
dump those whose instance variables have changed since the last dump.

Is it possible to somehow 'mark' an object when the value any of its
instance variables is changed?

I don't know whether this will be of practical use to you, but your
question got me wanting to brush up on Observable, so I did :slight_smile: See
below. It probably threads a bit too narrow a needle, but maybe it
will give you some ideas. And if someone knows an existing shortcut
for this, let me know :slight_smile:

I also recommend looking at observable.rb, in the standard libray,
just to see a great example of how powerful very simple Ruby can be.

require 'observer'

# A module which redefines attr_writer for classes that include it.
# The direct use of @observer_peers (the list of observers) is a bit
# inelegant, but I didn't want to keep adding the object watcher to
# the array over and over.

module AttrWriterInterceptor
   def self.included(m)
     m.class_eval do
       include Observable
       def self.attr_writer(*attrs)
         attrs.each do |att|
           define_method("#{att}=") do |value|
             @observer_peers ||=
             unless @observer_peers.include?(ObjectWatcher.instance)
               add_observer(ObjectWatcher.instance)
             end
             instance_variable_set("@#{att}", value)
             changed
             notify_observers(self)
           end
         end
       end
     end
   end
end

# A class that uses the observable attr_writers
class Item
   include AttrWriterInterceptor
   attr_writer :description
end

# A class to observe changes. It's a singleton to make it easier
# to add it as an observer (see above) without having to be passed
# an instance of it.

class ObjectWatcher
   require 'singleton'
   include Singleton

   attr_accessor :changed

   def initialize
     self.changed = true
   end

   def update(obj)
     self.changed = true
   end

   def dump_objects_if_changed
     if self.changed
       self.changed = false
       "Dumping objects!"
     else
       "Not dumping objects!"
     end
   end
end

if __FILE__ == $0

require 'test/unit'

class ObserverTest < Test::Unit::TestCase
   def setup
     @ow = ObjectWatcher.instance
     @i = Item.new
   end

# Test dumping at the beginning, then make sure the second dump
# doesn't happen, then change an attr and make sure it does happen
# the third time.
   def test_three_dumps
     assert(@ow.changed)
     assert_equal("Dumping objects!", @ow.dump_objects_if_changed)
     assert(!@ow.changed)
     assert_equal("Not dumping objects!", @ow.dump_objects_if_changed)
     @i.description = "Some item"
     assert(@ow.changed)
     assert_equal("Dumping objects!", @ow.dump_objects_if_changed)
   end
end

end

路路路

On Mon, 28 Aug 2006, Paul Murton wrote:

Paul Murton wrote:

Hi,

I have a whole load of objects which I would like to periodically dump
as YAML. However, in order to save on resources, I would prefer to only
dump those whose instance variables have changed since the last dump.

Is it possible to somehow 'mark' an object when the value any of its
instance variables is changed?

You can take a snapshot of the object and then do a comparsion later to
determine what's changes. Facets has snapshot.rb (which came orignially
from Michael Neumann's Wee) if you'd like to try it:

  http://facets.rubyforge.org/api/more/classes/Snapshot.html

Hmm... a #snapshot_compare method might make a good addition.

T.

Paul Murton wrote:

Hi,

I have a whole load of objects which I would like to periodically dump as YAML. However, in order to save on resources, I would prefer to only dump those whose instance variables have changed since the last dump.

If you're interested in knowing whether instance variables have changed since a known point in the past (eg when you last dumped the object), rather than the moment they change, something like this might work:

module InstanceVariableSnapshooter
  def clean!
    @__snapshot__ = ivar_hash
  end

  def clean?
    ivar_hash == @__snapshot__
  end

  def dirty?
    not clean?
  end

  private
  def ivar_hash
    instance_variables.inject({}) do | iv_hash, i_var |
      next iv_hash if i_var == '@__snapshot__'
      iv_hash[i_var] = instance_variable_get(i_var)
      iv_hash
    end
  end
end

Call clean! when the object is dumped to file, then call dirty? later to see if anything has changed. It's probably better to work via setter methods if you can, but this works direct with the variables.

Also, test whether this is less expensive than the dumping operation you're trying to avoid

alex

Hi --

路路路

On Mon, 28 Aug 2006, David Vallner wrote:

Paul Murton wrote:

I have a whole load of objects which I would like to periodically dump as YAML. However, in order to save on resources, I would prefer to only dump those whose instance variables have changed since the last dump.

Is it possible to somehow 'mark' an object when the value any of its instance variables is changed?

For the more "magical" way, you could play around with set_trace_func to monitor setters. Would probably be horribly, terribly slow.

It's probably best to have your objects trace changes by design. Modify the Object class to have a #dirty? and #clean! method, and alias / redefine the #attr_writer method to generate methods that will set the dirty flag when used?

I was thinking of something similar, but using Observable -- but the
problem is that it would only work for instance variable changes that
go through attr_writer.

David

--
http://www.rubypowerandlight.com => Ruby/Rails training & consultancy
   ----> SEE SPECIAL DEAL FOR RUBY/RAILS USERS GROUPS! <-----
http://dablog.rubypal.com => D[avid ]A[. ]B[lack's][ Web]log
Ruby for Rails => book, Ruby for Rails
http://www.rubycentral.org => Ruby Central, Inc.

This works if you .freeze the ivar returned by attr_reader to prevent
in-place modification.

jeremy

路路路

On 8/27/06, David Vallner <david@vallner.net> wrote:

It's probably best to have your objects trace changes by design. Modify
the Object class to have a #dirty? and #clean! method, and alias /
redefine the #attr_writer method to generate methods that will set the
dirty flag when used?

unknown wrote:

Hi --

monitor setters. Would probably be horribly, terribly slow.

It's probably best to have your objects trace changes by design. Modify the
Object class to have a #dirty? and #clean! method, and alias / redefine the
#attr_writer method to generate methods that will set the dirty flag when
used?

I was thinking of something similar, but using Observable -- but the
problem is that it would only work for instance variable changes that
go through attr_writer.

David

I appreciate all of the help so far. I had considered the possibility of
redefining attr_writer, but had hoped there might be a way of doing this
so that it would work for changes that DON'T go through attr_writer.

In fact this is actually quite important to me, because I am interested
in changes to ANY instance variable (including if a previously
non-existant instance variable is set), not just those made externally
accessible with a writer method. I should have been more clear about
this in my original post.

I've been thinking about this for ages now and my current thoughts are
that there may not be any way of getting quite what I want here :confused:

Any further suggestions appreciated

路路路

On Mon, 28 Aug 2006, David Vallner wrote:

--
Posted via http://www.ruby-forum.com/\.

unknown wrote:

Hi --

monitor setters. Would probably be horribly, terribly slow.

It's probably best to have your objects trace changes by design. Modify the
Object class to have a #dirty? and #clean! method, and alias / redefine the
#attr_writer method to generate methods that will set the dirty flag when
used?

I was thinking of something similar, but using Observable -- but the
problem is that it would only work for instance variable changes that
go through attr_writer.

David

I appreciate all of the help so far. I had considered the possibility of
redefining attr_writer, but had hoped there might be a way of doing this
so that it would work for changes that DON'T go through attr_writer.

In fact this is actually quite important to me, because I am interested
in changes to ANY instance variable (including if a previously
non-existant instance variable is set), not just those made externally
accessible with a writer method. I should have been more clear about
this in my original post.

I've been thinking about this for ages now and my current thoughts are
that there may not be any way of getting quite what I want here :confused:

Any further suggestions appreciated

class A
   # all method definitions here
   ...

   instance_methods(false).each do |m|
     m_obj = instance_method(m)
     define_method(m) do |*args|
       before = instance_variables.inject({}) { |h,k| h[k] = instance_variable_get(k); h }
       res = m_obj.bind(self).call(*args)
       after = instance_variables.inject({}) { |h,k| h[k] = instance_variable_get(k); h }
       return res
      end
   end
end

Salt and season to taste. (Note that the granularity is at the method level, not per statement, I'm assuming that's acceptable enough).

路路路

On Aug 27, 2006, at 5:37 PM, Paul Murton wrote:

On Mon, 28 Aug 2006, David Vallner wrote:

--
Posted via http://www.ruby-forum.com/\.