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.
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.
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.
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).
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)
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}
The child classes wouldn’t do
anything; They’d have to be hooked into the main program somehow…
troublesome and […]
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
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).
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:
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
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 forum is not affiliated to the Ruby language, Ruby on Rails framework, nor any Ruby applications discussed here.