Extending Methods

Hello all.

I’m working on a plugin system for a IRC client I’m currently making.
The current plan is for plugins to extend the base classes of the
program. Often a plugin will need to add a little bit of functionality
to an already existing method in an already existing class.

For example, I’ve got a logging plugin. I want it to extend the Channel
class to allow for logging of conversations. It needs to redefine
Channel’s initialize method to open a log file. But it can’t eliminate
all the important things the base initialize method did.

I’ve thought about doing this with alias, but I haven’t succeeded. My
thoughts were to rename ‘initialize’ to ‘old_initialize’, then have the
plugin redefine ‘initialize’. In code:

class Channel
alias old_initialize initialize
def initialize
old_initialize
# Then write custom code. (To open a log file, for example.)
end
end

This doesn’t work, however, because it leads to a stack overflow. I
think the reason for the overflow is because, when this is done twice,
old_initialize will call itself recursively with no end.

Anyone have any ideas for how this could be done? It doesn’t have to use
alias, but the general intention is thus: I want to redefine a method
and ‘copy and paste’ the old code from the method into the new method at
a given point. Sort of like using ‘super’, but no inheritence required.

Thanks! Responses are greatly appreciated.

On Apr 26, 2006, at 7:41 PM, Geoff S. wrote:

Anyone have any ideas for how this could be done? It doesn’t have
to use
alias, but the general intention is thus: I want to redefine a method
and ‘copy and paste’ the old code from the method into the new
method at
a given point. Sort of like using ‘super’, but no inheritence
required.

Can you please explain why inheritance and super are bad here? They
seem like the perfect solution to me.

James Edward G. II

James G. wrote:

Can you please explain why inheritance and super are bad here? They
seem like the perfect solution to me.

I don’t want to use inheritence and super because that would mean each
plugin would create its own child class of the base class it wants to
modify. This causes two problems: 1) The child classes wouldn’t do
anything; They’d have to be hooked into the main program somehow…
troublesome and 2) The child classes wouldn’t work together. If I have
two plugins modifying the same method in the same base class, they both
need to be able to modify it, not one overruling the other.

On Apr 26, 2006, at 9:26 PM, Geoff S. wrote:

I don’t want to use inheritence and super because that would mean each
plugin would create its own child class of the base class it wants to
modify. This causes two problems: 1) The child classes wouldn’t do
anything; They’d have to be hooked into the main program somehow…
troublesome and 2) The child classes wouldn’t work together. If I have
two plugins modifying the same method in the same base class, they
both
need to be able to modify it, not one overruling the other.

Sound’s like you need to make a more formal design to me then:

class Plugin
def initialize_delta(other_self)
# Stuff that needs to be in the initialize of the plugin
end
end

class NormalMainClass
def self.add_plugin(plugin)
@plugins ||= []
@plugins << plugin
end
def self.plugins
@plugins ||= []
end

def initialize
    # generic initialization here
    self.class.plugins.each do |plugin|
       instance_eval(&plugin.method(:initialize_delta))
    end
end

end

main part of program

load plugins from files

NormalMainClass.add_plugin(Plugin)

obj = NormalMainClass.new

Of course the way of outlined this may not be appropriate for what
you want to do, but it should give you some ideas. Just because ruby
can do a lot of stuff auto-magically doesn’t mean you have to, when
it’s not specific enough for you. (Note also this code is totally
untested, I may have message something up).

On Apr 27, 2006, at 3:26 AM, Geoff S. wrote:

  1. The child classes wouldn’t do
    anything; They’d have to be hooked into the main program somehow…
    troublesome and […]
  1. Use Class#inherited

class Parent
@@children = []
def self.children
@@children
end

def self.inherited(child)
@@children << child
end
end

class Child < Parent
end

class Child2 < Parent
end

Parent.children # => [Child, Child2]

initialize each plugin …

– Daniel

Hi –

On Thu, 27 Apr 2006, Daniel H. wrote:

def self.children

class Child2 < Parent
end

Parent.children # => [Child, Child2]

initialize each plugin …

Although…

class GrandChild < Child
end

Parent.children # => [Child, Child2, GrandChild]

You could get around it like this:

def self.inherited(child)
def child.inherited(grandchild)
end
@@children << child
end

:slight_smile: or maybe use an instance variable instead of a class variable,
since you really want to track the state of a single object:

class Parent
def self.children
@children
end

 def self.inherited(child)
   @children ||= []
   @children << child
 end

end

David


David A. Black ([email protected])
Ruby Power and Light, LLC (http://www.rubypowerandlight.com)

“Ruby for Rails” PDF now on sale! Ruby for Rails
Paper version coming in early May!

You can use Module and super

class Channel
def initialize
super if defined? super
end
end

module AChannelPlugin
def initialize
super if defined? super
# Then write custom code. (To open a log file, for example.)
end
end

channel = Channel.new
channel.extend AChannelPlugin # per-object plugin loading
Channel.include AChannelPlugin # global plugin loading

Just make sure you don’t forget to call super :stuck_out_tongue:

The main problem is that you can’t remove a plugin on-the-fly (you would
have to put a kind of per-module, per-instance enable/disable mechanism
by
hand)

Sylvain J.

On Thu, 27 Apr 2006, Geoff S. wrote:

all the important things the base initialize method did.
end

Thanks! Responses are greatly appreciated.

 harp:~ > cat a.rb
 class Module
   def def__ m, &b
     __m__, m = %W( __#{ m }__ #{ m } )

     unless(instance_method(__m__) rescue nil)
       alias_method __m__, m

       module_eval <<-code
         def #{ m }(*__a__, &__b__)
           ret = nil
           #{ __m__ }(*__a__, &__b__)
           __mblocks__ = self.class.__mblocks__['#{ m }']
           __mblocks__.each{|b| ret = instance_eval(&b)}
           ret
         end
       code
     end

     __mblocks__[m] << b

     self
   end
   def __mblocks__
     @__mblocks__ ||= Hash::new{|h,k| h[k] = []}
   end
 end

 class C
   def foo
     p 0
   end

   def__ 'foo' do
     p 1
   end
 end

 c = C.new
 c.foo
 puts

 C.def__(:foo){ p 2 }
 c.foo
 puts

 C.def__(:foo){ p 3 }.def__(:foo){ p self => 42 }
 c.foo
 puts



 harp:~ > ruby a.rb
 0
 1

 0
 1
 2

 0
 1
 2
 3
 {#<C:0xb75d05f8>=>42}

regards.

-a

Sylvain J. schrieb:


super if defined? super

Super! :slight_smile: I didn’t know that idiom. Thanks for sharing!

Regards,
Pit

Thanks for all the responses!

Daniel H. wrote:

On Apr 27, 2006, at 3:26 AM, Geoff S. wrote:

  1. The child classes wouldn’t do
    anything; They’d have to be hooked into the main program somehow…
    troublesome and […]
  1. Use Class#inherited

I was planning on using Class#inherited, but not for hooking each plugin
in. I was going to use it solely to generate a list of plugins. Each
plugin would be a .rb file that starts with a new class that inherits
from GenericPlugin, and that class defines the plugin, giving it a name
and description etc.

Sylvain J. wrote:

You can use Module and super

class Channel
def initialize
super if defined? super
end
end

module AChannelPlugin
def initialize
super if defined? super
# Then write custom code. (To open a log file, for example.)
end
end

channel = Channel.new
channel.extend AChannelPlugin # per-object plugin loading
Channel.include AChannelPlugin # global plugin loading

This is a nice solution, except for this: Every method that is inherited
(and those that aren’t can do it too) has to have that ‘super if
defined? super’ code in it. Perhaps that could be dynamically added to
each method as it is created, I’m not sure.

I’ve thought some more about it, and came up with this solution of my
own, that does in the end use super and inheritance.

class Foo
    def testage
        puts "This is the base Foo class"
    end
end

class FooPlugin < Foo
    def testage
        super
        puts "Hello from the FooPlugin"
    end
end
Foo = FooPlugin

class FooPlugin2 < Foo
    def testage
        super
        puts "Hello from FooPlugin2!"
    end
end
Foo = FooPlugin2

Foo.new.testage

# OUTPUT AS FOLLOWS:
#test2.rb:13: warning: already initialized constant Foo
#test2.rb:21: warning: already initialized constant Foo
#This is the base Foo class
#Hello from the FooPlugin
#Hello from FooPlugin2!

The only problem is those warnings. Is there a different way of
overwriting the Foo class with the FooPlugin class so that these
warnings are not produced? I really don’t want to have to resort to
running my program with -W0 (warning level: silent).

Thanks once again!

Geoff S. schrieb:

(… code reassigning a constant …)

The only problem is those warnings. Is there a different way of
overwriting the Foo class with the FooPlugin class so that these
warnings are not produced?

Geoff, just remove the constant before you overwrite it:

Object.instance_eval { remove_const :Foo }

Regards,
Pit

On Apr 29, 2006, at 10:58 AM, Geoff S. wrote:

The only problem is those warnings. Is there a different way of
overwriting the Foo class with the FooPlugin class so that these
warnings are not produced? I really don’t want to have to resort to
running my program with -W0 (warning level: silent).

Thanks once again!

Changing Foo, so we temporarily disable warnings

begin
old_verbosity = $VERBOSE
$VERBOSE = nil
Foo = FooPlugin2
ensure
$VERBOSE = old_verbosity
end

Pit C. wrote:

Geoff, just remove the constant before you overwrite it:

Object.instance_eval { remove_const :Foo }

Regards,
Pit

This works nicely. Thanks!

A small note, for the record. The class that has the method that I want
to extend (corresponds to Foo in this example) actually lives within an
IRC module within my program. Thus, the code I’m using looks like:
IRC.instance_eval { remove_const :Channel }
IRC::Channel = ChannelPlugin

Thanks once again!