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:

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 V.

Hi –

On Mon, 28 Aug 2006, David V. wrote:

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

unknown wrote:

Hi –

On Mon, 28 Aug 2006, David V. wrote:

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 8/27/06, David V. [email protected] 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?

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

jeremy

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 N.'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.

Hi –

On Mon, 28 Aug 2006, 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?

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

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

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

redefine the

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).