Forward object proxies && intercepting every method call and instvar write on any object


I wrote universal object storage for Smalltalk (very dynamic language) as my hobby project and I just thinking, if it’s possible in Crystal.

For example in Smalltalk, when I need to load object (of any class) from data store (database), I just instantiate empty object of target class (from stored class name string) and then create a forward proxy object which targets this prepared empty object instance. My data model is then full of such forward proxies instead of real data objects.

Now, real object instvar loading in my Smalltalk project - when the first method call (method send) is made on a forward proxy (forward proxy knows OID of target object in the data store), doesNotUnderstand (something like method_missing) is called on my forward proxy, then I can load all instvars from data store to the empty proxy target object and then I can forward method call/send to the (now fully loaded) target object. So, whole data model object tree is loaded lazily from the data store just after first method call/send on forward proxies. I am pretty happy with it and everything works perfectly.

I hope it’s clear.

I am learning all the macros and metaprogramming things in Crystal right now - what do you think, is something similar possible in Crystal, or it’s rather not possible in statically compiled languages (even with macro tricks)? Thanks for your opinions!

1 Like

Hello again, I found, that I can probably deal with forward proxies via method_missing, so I have one remaining question - can I detect instance variable write? It’s not about redefining setters with macros, I need to detect real instvar write, because all object methods can change instvars, not only setters.

There is no way to detect a change on ivar. There is no hook or callback.

There are a couple of limits of a proxy implemented using method_missing without ProxyObject < RealObject:

  • responds_to? might not return what you expect. If will depend if the def was “expanded” before the call to responds_to? AFAIK.
  • type restrictions of def foo(o : RealObject) will not allow you to pass a ProxyObject.
  • Same as above but when an ivar is declared to store a RealObject.

These depends on how RealObject is used, so it might or might not be a problem depending on your use case.

Some hooks for instvar accesses would be nice. Thanks for the detailed answer!

What is the use case where you need that hook?

You could have a macro to set the ivar and call a hook following some convention, but is up to the programmer to use it.

Also, although is not encouraged, there is nothing that stop you from doing foo.@x = 1 from outside Foo class. Since classes can be reopened we found no real value in disallowing foo.@x expressions.

Use case? With instvar write hook, I can detect, that instvar has changed in the transaction scope, so I can store the changed instvars of the object instance back to the data store at the end of transaction scope.