Best practice for redefining methods at runtime?

I have a class that I would like to “freeze” when it reaches a
particular state so that it becomes read-only. I do not want to use
the existing #freeze method because it raises an exception when there is
an attempted mutation on a frozen object and I want the attempt to be
silent. It appears that the best way to enforce this (state machine
pattern) is by redefining the accessors and other methods that cause
mutations to be no ops.

What is the preferred practice for doing that?

Here’s an example of my approach:

class Foo
attr_accessor :bar, :baz, :quxxo

def freeze!
do_nothing = Proc.new {|val| nil}
recreate_method(:bar=, do_nothing)
recreate_method(:baz=, do_nothing)
recreate_method(:quxxo=, do_nothing)
end

private

def recreate_method name, meth
self.class.send(:define_method, name, meth)
nil
end
end

ruby-1.9.2-p0 > f=Foo.new
=> #Foo:0x0000010185f7d0
ruby-1.9.2-p0 > f.bar=2
=> 2
ruby-1.9.2-p0 > f.baz=9
=> 9
ruby-1.9.2-p0 > f.freeze!
=> nil
ruby-1.9.2-p0 > f.inspect
=> “#<Foo:0x0000010185f7d0 @bar=2, @baz=9>”
ruby-1.9.2-p0 > f.bar=3
=> 3
ruby-1.9.2-p0 > f.inspect
=> “#<Foo:0x0000010185f7d0 @bar=2, @baz=9>”

Is that reasonable or is there a more idiomatic way of accomplishing
this task?

cr

the only best practice I would advise is to not redefine methods at
run-time.

Morning Chuck.

On Wed, Oct 20, 2010 at 8:31 AM, Chuck R. [email protected]
wrote:

I have a class that I would like to “freeze” when it reaches a particular
state so that it becomes read-only. I do not want to use the existing
#freeze method because it raises an exception when there is an attempted
mutation on a frozen object and I want the attempt to be silent. It appears
that the best way to enforce this (state machine pattern) is by redefining
the accessors and other methods that cause mutations to be no ops.

What is the preferred practice for doing that?

You could in the alternative have the ops that mutate do an “unless” at
the
top of the method to bake the freeze into the method as opposed to
redefining

Using your example

class Foo
attr_accessor :frozen
attr_reader :bar, :baz, :quxxo

def bar= new_bar
unless @frozen @bar = new_bar
end

def baz= new_baz
unless @frozen @baz = new_baz
end

def quzzo= new_quzzo
unless @frozen @quzzo = new_quzzo
end
end

It’s a little more work and a slight amount of overhead for the unless
check. But it does eliminate the need to redefine your methods. It also
very
clearly spells out what you are freezing.

John

On Oct 20, 2010, at 1:29 PM, John W Higgins wrote:

What is the preferred practice for doing that?

You could in the alternative have the ops that mutate do an “unless” at the
top of the method to bake the freeze into the method as opposed to
redefining

Thanks for everyone for their suggestions. Instead of using
metaprogramming, which in this case is apparently quite problematic,
I’ll go with the easiest solution of doing an “unless” check in certain
key methods. Obviously that won’t prevent a future developer from adding
singleton methods to the object(s) or messing with them in some other
way, but that will clearly break the contract. The docs will say “don’t
do this” and leave it at that.

Thanks again.

cr

On Wed, Oct 20, 2010 at 5:31 PM, Chuck R. [email protected]
wrote:

Is that reasonable or is there a more idiomatic way of accomplishing this task?
Reasonable maybe but it is far from achieving what freeze does,
because you still have instance_variable_set and as long as this
available I can redefine the accesors, thus you would need to block
#instance_variable_set, #define_method for the class but I can still
beat that for any given instance by introducing singleton methods…
In other words it is a metaprogramming mess.sub(“m”,“h”).gsub(“s”,“l”)!

Maybe you should reconsider your approach, what about freezing the
object but accessing it in a wrapper that rescues the exceptions? This
could done via closures, and BasicObject makes the task much easier
than it was in 1.8

class Freezer < BasicObject
freezee = nil
define_method :initialize do | f |
freezee = f
end
define_method :method_missing do | name, *args, &blk |
begin
freezee.send( name, *args, &blk )
rescue ::RuntimeError => re
# Pitty that there is no designated Exception for this BTW
raise unless /can’t modify frozen/ === re.message
end
end
end

a = Freezer.new( “” )
a << “1”
puts a
a.freeze
a << “2”
puts a

But

a.xxx

HTH
R.

On Wed, Oct 20, 2010 at 8:29 PM, John W Higgins [email protected]
wrote:

class Foo

def quzzo= new_quzzo
unless @frozen @quzzo = new_quzzo
end
end

It’s a little more work and a slight amount of overhead for the unless
check. But it does eliminate the need to redefine your methods. It also very
clearly spells out what you are freezing.

But why do this if you can simply use #freeze and #frozen? as they are
defined? The advantage of the regular #freeze method is that you will
immediately notice if you try to modify a frozen instance. All
approaches present here will silently eat the method call and do
nothing thus making the caller believe the method has worked regularly
while it hasn’t. I think this approach to freezing is error prone and
likely to create hard to detect bugs.

Kind regards

robert