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.

···

--
Posted via http://www.ruby-forum.com/.

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

James Edward Gray II

···

On Apr 26, 2006, at 7:41 PM, Geoff Stanley 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.

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

···

On Thu, 27 Apr 2006, Geoff Stanley wrote:

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.

--
be kind whenever possible... it is always possible.
- h.h. the 14th dali lama

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 Joyeux

--
Sylvain Joyeux

James Gray 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.

···

--
Posted via http://www.ruby-forum.com/\.

Sylvain Joyeux schrieb:

...
  super if defined? super
...

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

Regards,
Pit

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 26, 2006, at 9:26 PM, Geoff Stanley 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.

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

···

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

1) The child classes wouldn't do
anything; They'd have to be hooked into the main program somehow...
troublesome and [...]

Hi --

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 ...

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

···

On Thu, 27 Apr 2006, Daniel Harple wrote:

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

--
David A. Black (dblack@wobblini.net)
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!

Thanks for all the responses!

Daniel Harple 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 Joyeux 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!

···

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

--
Posted via http://www.ruby-forum.com/\.

# Changing Foo, so we temporarily disable warnings
begin
   old_verbosity = $VERBOSE
   $VERBOSE = nil
   Foo = FooPlugin2
ensure
   $VERBOSE = old_verbosity
end

···

On Apr 29, 2006, at 10:58 AM, Geoff Stanley 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!

Geoff Stanley 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

Pit Capitain 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!

···

--
Posted via http://www.ruby-forum.com/\.