Forum: Ruby Extending Methods

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
C2bcdb28414d5ca01b0b4c03aba7e62c?d=identicon&s=25 Geoff Stanley (minsc)
on 2006-04-27 02:41
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.
4299e35bacef054df40583da2d51edea?d=identicon&s=25 James Gray (bbazzarrakk)
on 2006-04-27 02:54
(Received via mailing list)
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.

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

James Edward Gray II
C2bcdb28414d5ca01b0b4c03aba7e62c?d=identicon&s=25 Geoff Stanley (minsc)
on 2006-04-27 03:26
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.
E34b5cae57e0dd170114dba444e37852?d=identicon&s=25 Logan Capaldo (Guest)
on 2006-04-27 03:40
(Received via mailing list)
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.
>

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).
9358cc96c46055cd68d4a76a9aefe026?d=identicon&s=25 Daniel Harple (Guest)
on 2006-04-27 03:43
(Received via mailing list)
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 [...]

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
1fba4539b6cafe2e60a2916fa184fc2f?d=identicon&s=25 unknown (Guest)
on 2006-04-27 03:59
(Received via mailing list)
Hi --

On Thu, 27 Apr 2006, Daniel Harple 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

:-) 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 (dblack@wobblini.net)
Ruby Power and Light, LLC (http://www.rubypowerandlight.com)

"Ruby for Rails" PDF now on sale!  http://www.manning.com/black
Paper version coming in early May!
Cb48ca5059faf7409a5ab3745a964696?d=identicon&s=25 unknown (Guest)
on 2006-04-27 06:14
(Received via mailing list)
On Thu, 27 Apr 2006, Geoff Stanley 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
934180817a3765d132193a5428f99051?d=identicon&s=25 Sylvain Joyeux (Guest)
on 2006-04-27 08:40
(Received via mailing list)
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 :p

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
93d566cc26b230c553c197c4cd8ac6e4?d=identicon&s=25 Pit Capitain (Guest)
on 2006-04-27 10:09
(Received via mailing list)
Sylvain Joyeux schrieb:
> ...
> 	super if defined? super
> ...

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

Regards,
Pit
C2bcdb28414d5ca01b0b4c03aba7e62c?d=identicon&s=25 Geoff Stanley (minsc)
on 2006-04-29 16:58
Thanks for all the responses!

Daniel Harple wrote:
> 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 [...]
>
> 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!
E34b5cae57e0dd170114dba444e37852?d=identicon&s=25 Logan Capaldo (Guest)
on 2006-05-03 18:56
(Received via mailing list)
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!

# Changing Foo, so we temporarily disable warnings
begin
   old_verbosity = $VERBOSE
   $VERBOSE = nil
   Foo = FooPlugin2
ensure
   $VERBOSE = old_verbosity
end
93d566cc26b230c553c197c4cd8ac6e4?d=identicon&s=25 Pit Capitain (Guest)
on 2006-05-03 18:57
(Received via mailing list)
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
C2bcdb28414d5ca01b0b4c03aba7e62c?d=identicon&s=25 Geoff Stanley (minsc)
on 2006-05-06 21:43
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!
This topic is locked and can not be replied to.