I’ve modified my version by getting inspired by your code
In this case there is no stack. You can remove any arbitrary
behaviour/module, which can be an advantage or a risk.
module Behaviourable
def extend mod
@ancestors ||= {}
return if @ancestors[mod]
mod_clone = mod.clone
@ancestors[mod] = mod_clone
super mod_clone
end
def remove mod
mod_clone = @ancestors[mod]
mod_clone.instance_methods.each {|m| mod_clone.module_eval {
remove_method m } }
@ancestors[mod] = nil
end
end
class A
include Behaviourable
end
module NormalBehaviour
def talk; puts ‘Hi’; end
end
module EnhancedBehaviour
def talk; super; puts ‘Goodbye’; end
end
a = A.new
a.extend NormalBehaviour
a.talk # Hi
a.extend EnhancedBehaviour
a.talk # Hi
# Goodbye
a.remove EnhancedBehaviour
a.talk # Hi
a.extend EnhancedBehaviour
a.extend EnhancedBehaviour # Nothing happens
a.talk # Hi
# Goodbye
Robert D. wrote:
On Mon, Sep 1, 2008 at 10:34 PM, Jos� Ignacio
[email protected] wrote:
Thanks, that’s a really nice piece of code that helps a lot
Why use a @bsp variable instead of using @behavior_stack.pop or
@behavior_stack.last ?
Good question ;). Actually I am leaving empty modules on the stack
that can be reused, thus
after a push_behavior the stack might look like this [ x, module with
the methods from pushed behavior ] and the sp is 1
after pop_behavior the situation is as follows stack : [x, empty
module] and the sp=0.
This avoids unnecessary recreation of modules ( and lets the GC rest,
but that was not the idea
Nice That’s not the case in my code, where objects are created and
deleted everytime you add or remove a behaviour.
It seems that the trick to undo Object.extend is done by erasing all
instance methods in the modules. Isn’t this a bit strange? If Ruby
allows including modules in an instance’s ancestor chain, there should
be a way to remove them…
I am not sure to understand this question, do you mean removing them
from the includee?
That would not work, sorry if I miss the obvious here maybe you might
elaborate.
Trouble is that I don’t really know what happens in the background in
Ruby when you use Object.extend.
I suppose an internal module chain is kept in instances in a similar way
as it happens with classes:
A.ancestors # => [A, Behaviourable, Object, Kernel]
a.extend NormalBehaviour # should make something like a.internal_chain =
[NormalBehaviour.clone, A, Behaviourable, Object, Kernel]
If we remove all methods from that NormalBehaviour.clone and re-extend
the object with that module, the chain will grow with an empty module:
a.remove NormalBehaviour # a.internal_chain =
[NormalBehaviour.clone.empty!, A, Behaviourable, Object, Kernel]
a.extend NormalBehaviour # a.internal_chain = [NormalBehaviour.clone,
NormalBehaviour.clone.empty!, A, Behaviourable, Object, Kernel]
But that’s my way of viewing this… I don’t really know what happens in
the background
Cheers
Jose