Inherited & load


#1

Hello.

I’ve written a mechanism to dynamically load plugins inheriting from a
base class, using the self.inherited method.

Now, in an irb session, I noticed that, even if I (re)load the classes,
the self.inherited is only triggered once. Short example :

ruby -v
ruby 1.8.6 (2007-03-13 patchlevel 0) [i386-freebsd6]

cat first.rb
#! /usr/local/bin/ruby
load “test.rb”
load “test.rb”

cat test.rb
#! /usr/local/bin/ruby
class Test
def self.inherited©
puts “#{c} inherited Test !”
end
end
load “test2.rb”

cat test2.rb
#! /usr/local/bin/ruby
puts “In #{FILE}.”
class Test2 < Test ; end

ruby first.rb
In ./test2.rb.
Test2 inherited Test !
In ./test2.rb.

Is there a trick somewhere that would allow me to trigger the inherited
method at each time the file containing the “sub-class” (e.g. Test2) is
loaded ?

(The real plugin class, if there is a need, is here :
http://www.lacave.net/~fred/projets/plugin.rb )

Fred


#2

On 7/24/07, F. Senault removed_email_address@domain.invalid wrote:

end
Test2 inherited Test !
In ./test2.rb.

Is there a trick somewhere that would allow me to trigger the inherited
method at each time the file containing the “sub-class” (e.g. Test2) is
loaded ?
As Todd pointed out already, “inheritance” will happen only once,
might it be that you have chosen a suboptimal mechanism :wink: for what
you want to achieve?
Maybe you could tell us a little bit more about that?

Cheers
Robert


#3

On 7/23/07, F. Senault removed_email_address@domain.invalid wrote:

end
Test2 inherited Test !
In ./test2.rb.

Is there a trick somewhere that would allow me to trigger the inherited
method at each time the file containing the “sub-class” (e.g. Test2) is
loaded ?

(The real plugin class, if there is a need, is here :
http://www.lacave.net/~fred/projets/plugin.rb )

Fred

This is probably not much help, but you got me to learn a couple of
things while playing around.

First of all, I don’t think you can inherit more than once without
removing the constant. Once you’ve declared class B < A then that’s
it until removed. For example,

irb> class A; def self.inherited(s); puts “#{s}!”; end; end
=> nil
irb> class B < A; end
B!
=> nil
irb> class B < A; end
=> nil
irb> class C;end
=> nil
irb> class B < C; end
=>TypeError: superclass mismatch for class B
…blah blah

But, if you use a module,

$ cat m.rb
module M
class A
def self.inherited o
puts “A inherited by #{o}!”
end
end
def self.delete const
remove_const const.intern
end
end

$ cat test.rb
require “m”

puts “\nConstants: #{M.constants.inspect}”
print "First inherit: "
class M::B < M::A; end

puts “\nConstants: #{M.constants.inspect}”
print "Second try at inherit: "
class M::B < M::A; end

print “\n\nDelete B”
M::delete “B”
puts “\nConstants: #{M.constants.inspect}”
print “Try inherit now:”
class M::B < M::A; end
puts
$

Todd


#4

On 7/24/07, Todd B. removed_email_address@domain.invalid wrote:

ruby 1.8.6 (2007-03-13 patchlevel 0) [i386-freebsd6]
puts “#{c} inherited Test !”
In ./test2.rb.
Fred
irb> class B < A; end
But, if you use a module,
end
print "Second try at inherit: "
Todd

Hmm not sure how this fits in but what about include instead of
inherit, does this not what you want to achieve?
The more I am thinking about it I feel that this might indeed be what
is needed as Module#included is executed all the time the include
statement is called, hopefully Fred can use inclusion instead of
inheritance.

Cheers
Robert


#5

Le 24 juillet 2007 à 01:25, Todd B. a écrit :

This is probably not much help, but you got me to learn a couple of
things while playing around.

Good ! :slight_smile:

First of all, I don’t think you can inherit more than once without
removing the constant. Once you’ve declared class B < A then that’s
it until removed. For example,

Thats funny. I can find the constant in Object.constants, but there is
no delete_const in there. :expressionless:

Fred


#6

Le 24 juillet 2007 à 10:02, Robert D. a écrit :

On 7/24/07, F. Senault removed_email_address@domain.invalid wrote:

Is there a trick somewhere that would allow me to trigger the inherited
method at each time the file containing the “sub-class” (e.g. Test2) is
loaded ?
As Todd pointed out already, “inheritance” will happen only once,
might it be that you have chosen a suboptimal mechanism :wink: for what
you want to achieve?
Maybe you could tell us a little bit more about that?

The full code is there :

http://www.lacave.net/~fred/projets/plugin.rb

It’s working behind the scenes in this project :

http://talisker.lacave.net/news/ngraph/

Basically, I have a Plugin class, which implements a few methods,
including at the class level.

Then, I reuse this class in a few projects, by inheriting it in a base
class. For instance, I can write :

class Tester < Plugin
plugin_must_implement :register
plugin_must_implement :getdata
plugin_load_glob “./testers/[a-zA-Z0-9].rb”

methods

end

Then, when I use Tester.load!, all the files in the specified path are
load’ed, and all the classes inheriting from Tester are registered as
plugins of that class, validated against the obligatory methods, and,
unless told otherwise, an instance of each is created and made available
and enumerable through an array. Then, I can use, for instance :

Tester.each do |t|
t.register
d = t.getdata

process the tester data

end

In the concrete case of my newsserver, I collect data to make the graphs
from different sources. So, when I have new data, instead of modifying
the main body of the grapher program, I can add a new plugin in a small
rb file (averaging 25 lines) and it’s “magically” taken into account…

I’ve already managed to reuse this class in at least four different
projects, and I kind of like the concept… :slight_smile:

Now, in the event of a long running process, I would like to “hotplug”
the plugins, and, so, be able to simply reload everything. My problem
is that I don’t see how I can find the classes contained in a file I
just loaded without playing with inherited / Object.constants.

My current working idea is this one :

#! /usr/local/bin/ruby

#module Toto
class Test
class <<self
attr_accessor :loaded_classes
def do_load
@loaded_classes ||= {}
[ “test2.rb”, “test3.rb” ].each do |f|
o = Object.constants
load f
( Object.constants - o ).each do |c|
t = Object.const_get©
if t.is_a? Class then
if t.superclass == Test then
@loaded_classes[f] ||= []
@loaded_classes[f] << t
end
end
end
end
end
end
end

Test.do_load
p Test.loaded_classes
Test.do_load
p Test.loaded_classes

Needless to say, I still believe inherited was a bit simpler… :slight_smile:

(And it doesn’t cope with classes being suppressed.)

Fred


#7

Le 24 juillet 2007 à 12:20, Robert D. a écrit :

On 7/24/07, F. Senault removed_email_address@domain.invalid wrote:

end
Now you can be sure that
Plugin#included / Plugin#extended
is executed evry time you load a file.
Did I miss something?
Oh yes of course might be a pain to redesign :(.

Yup, basically, I didn’t know I’d hit that problem with included, and it
will be a pain to redesign.

OTOH, I can’t manage to use included neither :

#! /usr/local/bin/ruby

module Plugin
def self.included(m)
puts “#{m} includes me.”
end
end

class TestA
include Plugin
end
class TestA1 < TestA ; end

Just gives “TestA includes me”… :expressionless:

Fred


#8

On 7/24/07, F. Senault removed_email_address@domain.invalid wrote:

Then, when I use Tester.load!, all the files in the specified path are
load’ed, and all the classes inheriting from Tester are registered as
plugins of that class, validated against the obligatory methods, and,
unless told otherwise, an instance of each is created and made available
and enumerable through an array.
I do not see any particular reason why not to use Module inclusion;
Instead of
class Tester < Plugin
class Tester
include Plugin ## or maybe extend, I have not looked into the details
end
Now you can be sure that
Plugin#included / Plugin#extended
is executed evry time you load a file.
Did I miss something?
Oh yes of course might be a pain to redesign :(.

Cheers
Robert
Then, I can use, for instance :


#9

On Jul 24, 2007, at 5:10 AM, F. Senault wrote:

I can find the constant in Object.constants, but there is
no delete_const in there. :expressionless:

$ ri -T remove_const
---------------------------------------------------- Module#remove_const
remove_const(sym) => obj

  Removes the definition of the given constant, returning that
  constant's value. Predefined classes and singleton objects

(such as
true) cannot be removed.

Hope that helps.

James Edward G. II


#10

On 7/24/07, F. Senault removed_email_address@domain.invalid wrote:

Instead of
Yup, basically, I didn’t know I’d hit that problem with included, and it
end

class TestA
include Plugin
end
class TestA1 < TestA ; end
class TestA1
include Plugin

But that will not do the trick, you need meta information on class
level, I just have tried to adapt update.rb for usage with extend

class Test
extend Plugin

class Test1
extend Plugin

I will send the patch (err the new file the patch is longer ) off list.
Maybe you want to consider its deployment maybe not, was fun reading
your code and your ideas, thx 4 sharing.

Cheers
Robert


#11

On Jul 24, 2007, at 9:40 AM, F. Senault wrote:

Alas, Object is not a module :
Did you ask it? :wink:

Object.is_a? Module
=> true

Object.class
=> Class

Object.class.ancestors
=> [Class, Module, Object, Wirble::Shortcuts, PP::ObjectMixin, Kernel]

16:35 fred@balvenie:~/ruby/espions> irb

o = Object.constants
=> [“SocketError”, …]

class Test ; end
=> nil

Object.constants - o
=> [“Test”]

Object.remove_const(:Test)
NoMethodError: private method `remove_const’ called for Object:Class
from (irb):4

Object.constants.grep(“Test”)
=> []

class Test; end
=> nil

Object.constants.grep(“Test”)
=> [“Test”]

Object.send(:remove_const, “Test”)
=> Test

Object.constants.grep(“Test”)
=> []

Hope that helps.

James Edward G. II


#12

On 7/24/07, James Edward G. II removed_email_address@domain.invalid wrote:

=> []

Hope that helps.

James Edward G. II

Ah. I forgot about Object#send. Thanks, James.

Todd


#13

Le 24 juillet à 16:45, James Edward G. II a écrit :

On Jul 24, 2007, at 9:40 AM, F. Senault wrote:

Object.constants.grep(“Test”)
=> [“Test”]

Object.send(:remove_const, “Test”)
=> Test

Object.constants.grep(“Test”)
=> []

Hope that helps.

Thanks a lot for the migraine… I guess… :slight_smile:

But, yes, it’s beginning to work with self.inherited, which gives the
most elegant solution IMHO :

test.rb :

#! /usr/local/bin/ruby

class Test
class <<self
attr_accessor :loaded_classes
def inherited©
puts “#{c} inherited me !”
@loaded_classes ||= []
@loaded_classes << c
end
end
end

load “test2.rb”

Test.loaded_classes.each do |c|
Object.send(:remove_const, c.to_s)
end

load “test2.rb”

test2.rb :

#! /usr/local/bin/ruby

puts “In #{FILE}.”

class Test2a < Test ; end
class Test2b < Test ; end

ruby test.rb
In ./test2.rb.
Test2a inherited me !
Test2b inherited me !
In ./test2.rb.
Test2a inherited me !
Test2b inherited me !

Yay.

No, I just have to include that in my plugin system to allow for
“hotplugging”… Hehehehe…

Fred


#14

Le 24 juillet à 17:11, F. Senault a écrit :

No, I just have to include that in my plugin system to allow for
“hotplugging”… Hehehehe…

For the curious / interested :

http://www.lacave.net/~fred/projets/plugin.rb

Works like a charm. I’ve added reload methods and the ability to
auto-reload.

Fred


#15

Le 24 juillet à 15:18, James Edward G. II a écrit :

On Jul 24, 2007, at 5:10 AM, F. Senault wrote:

I can find the constant in Object.constants, but there is
no delete_const in there. :expressionless:

$ ri -T remove_const
Hope that helps.

Alas, Object is not a module :

16:35 fred@balvenie:~/ruby/espions> irb

o = Object.constants
=> [“SocketError”, …]

class Test ; end
=> nil

Object.constants - o
=> [“Test”]

Object.remove_const(:Test)
NoMethodError: private method `remove_const’ called for Object:Class
from (irb):4

Fred