Auto-serialization (as in Vapor, sql-serialize, etc.): detectingchanges to instance variables

J

Jim Cain

All,

I'm working on the same problem as has been encountered in the projects
named above: It would be very handy to have a way to trap changes to an
instance variable... perhaps a callback in the same spirit as
Kernel#trace_var. To be really useful it would have to be attached to
the instance variable itself, not just to the accessor methods.

There is a problem even with this low level of control, however. Suppose
you have a reference R outside of a particular object S that you're
interested in serializing, and that the reference R points to an object
that one of S's instance variables points to. In other words, this is true:

R.equal?(S.iv)

How can you detect, from S, that a change to S.iv has been made through
R, without doing something like comparing the before and after
serializations of S? Would it be possible to create a callback that was
attached to the referred-to object (*R or *S.iv in C-speak), rather than
to the instance variable?

Food for thought.

Cheers,
Jim
 
A

Anders Borch

Jim said:
All,

I'm working on the same problem as has been encountered in the projects
named above: It would be very handy to have a way to trap changes to an
instance variable... perhaps a callback in the same spirit as
Kernel#trace_var. To be really useful it would have to be attached to
the instance variable itself, not just to the accessor methods.

There is a problem even with this low level of control, however. Suppose
you have a reference R outside of a particular object S that you're
interested in serializing, and that the reference R points to an object
that one of S's instance variables points to. In other words, this is true:

R.equal?(S.iv)

How can you detect, from S, that a change to S.iv has been made through
R, without doing something like comparing the before and after
serializations of S? Would it be possible to create a callback that was
attached to the referred-to object (*R or *S.iv in C-speak), rather than
to the instance variable?

I think you should not allow anyone to modify your instance variables
without going through your accessor methods. It ruins everything that
encapsulation gives you.

With that out of the way I have heard of method:before and method:after
callbacks comming (are they in ruby 1.8?) - they would help you to
transparently test if serialization is needed. Again, this depends on
using accessor methods.

Another way to do it would be to wrap the object like I have done in the
supplied example. The idea, in this example, is to load an object as
late as possible which *might* save us from fetching some of your
objects from a database. Imagine fetching a large array of objects where
you in reality only need a few of those objects. This wrapper object
would postpone the loading of the object until it was needed.

-- 8< --
# code to test the idea of a wrapper object
class SomeObject
attr_accessor :myvar
end

class Wrapper
def method_missing(meth, *args)
if (@obj.nil?)
@obj = SomeObject.new # load the object from the database here
end
@obj.__send__(meth, *args)
end
end

w=Wrapper.new # SomeObject isn't loaded yet
w.myvar = 'hej' # now we need the object so we load it...
p w.myvar # the loaded object is called once again
-- 8< --

Using the method_missing to test if the object has changed after each
method call *could* solve your problem. You should be aware, tho, that
there could be a serious performance hit with this approach. Imagine a
Person object that needs a lot of changes:

p.first_name = "Anders" # update in db once first_name= has been called
p.last_name = "Borch" # update in db again
p.zip_code = 90210 # update in db again

This *may* not be what you really want. Rather I suggest that you have
some #checkpoint method that serializes the object, and you may want to
call that in an object finalizer.

/Anders
 
A

Andrey Kulinich

Anders said:
I think you should not allow anyone to modify your instance variables
without going through your accessor methods. It ruins everything that
encapsulation gives you.

Sometimes you can't control modification.

class Session
attr_accessor :modified
def initialize
@data = {}
@modified = false
end

def []( key )
@data[key]
end

def []=( key, value )
@data[key] = value
@modified = true
end
end

session = Session.new
session[:arr] = []
session.modified # true. all ok
session.modified = false

session[:arr].push( 1 )
session.modified # false :(
Using the method_missing to test if the object has changed after each
method call *could* solve your problem.

There is a trouble with method_missing: you can't redirect methods
'to_s', '==' and other Object's methods, because they are defined in
Wrapper.

--
with best regards,
Andrey Kulinich
IT Group
Software developer
phone/fax +380 (372) 58-43-10
e-mail: (e-mail address removed)
http://www.itgrp.net
 
A

Anders Borch

I haven't come up with a good solution to this myself, I'm merely
suggesting, that maybe you are going about this the wrong way. I once
read that if you are experiencing extreme difficulty with the
implementation you are attempting then maybe you are attempting the
wrong implementation. Very often there is an easy solution to an
otherwise hard problem.

Andrey said:
Anders said:
I think you should not allow anyone to modify your instance variables
without going through your accessor methods. It ruins everything that
encapsulation gives you.


Sometimes you can't control modification.

class Session
attr_accessor :modified
def initialize
@data = {}
@modified = false
end

def []( key )
# this may not ALWAYS be appropriate, but if you are
# concerned with encapsulation you may want to consider it...
d = @data[key].dup
d.freeze
return d
@data[key]
end

def []=( key, value )
@data[key] = value
@modified = true
end
end

session = Session.new
session[:arr] = []
session.modified # true. all ok
session.modified = false

session[:arr].push( 1 )
session.modified # false :(
Using the method_missing to test if the object has changed after each
method call *could* solve your problem.


There is a trouble with method_missing: you can't redirect methods
'to_s', '==' and other Object's methods, because they are defined in
Wrapper.

then overload ==, to_s and the rest of Object's methods to make sure
they are passed on to the wrapped object.

while freezing the return value of [] may not be a good idea,
overloading the rest of Object's methods is certainly a nice and easy
solution to most of your problems.

/Anders
 

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,755
Messages
2,569,536
Members
45,011
Latest member
AjaUqq1950

Latest Threads

Top