Detecting when an object's instance variables are modified

P

Paul Murton

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 :)
 
D

David Vallner

Paul said:
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
 
D

dblack

Hi --

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

Paul Murton

unknown said:
Hi --



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 :/

Any further suggestions appreciated
 
D

dblack

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

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([email protected])
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
 
T

Trans

Paul said:
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.
 
A

Alex Fenton

Paul said:
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
 
L

Logan Capaldo

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 :/

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

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,581
Members
45,057
Latest member
KetoBeezACVGummies

Latest Threads

Top