There is a way - a hacky (but fun) way which is not meant for production (and is relatively slow). My sample implementation works for a single object only, but can be extended to support many objects.
Let's assume the following setup:
class Foo
def initialize
@a = :foo
end
def set_b; @b = 3; end
def set_c; @c = 7; end
end
def freeze_variables_of(obj)
frozen_variables = obj.instance_variables
set_trace_func lambda {|event, file, line, id, binding, classname|
if classname == obj.class
this = binding.eval 'self'
if this == obj
(this.instance_variables - frozen_variables).each {|var| this.remove_instance_variable var}
end
end
}
end
With the use of set_trace_func
we can set a Proc which is called very often (usually more than once per statement). In that Proc we can check instance variables and remove unwanted variables.
Let's look at an example:
a = Foo.new
# => #<Foo:0x007f6f9db75cc8 @a=:foo>
a.set_b; a
# => #<Foo:0x007f6f9db75cc8 @a=:foo, @b=3>
freeze_variables_of a
a.set_c; a
# => #<Foo:0x007f6f9db75cc8 @a=:foo, @b=3>
We see that after doing the "freeze", set_c
cannot set the instance variable (in fact the variable is removed at the very moment the set_c
method returns).
In contrast to freezing the object (with a.freeze
) (which I'd recommend for any real world application), this way allows you to modify all allowed instance variables while forbidding new ones.
This even works, if you directly assign instance variables (while Alex' method is probably faster, but relies on accessor methods).