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
http://www.manning.com/black => 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/.