Inherited & load

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(c)
    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 :
lacave.net )

Fred

···

--
Arithmetic comparisons: greater-than and less-than considered to be
judgemental and divisive. In the interests of inclusiveness, only
equality-tests will be permitted.
                      (Tanuki in the SDM, on politically-correct coding)

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"

> cat test.rb
#! /usr/local/bin/ruby
class Test
  def self.inherited(c)
    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

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,

class A; def self.inherited(s); puts "#{s}!"; end; end

=> nil

class B < A; end

B!
=> nil

class B < A; end

=> nil

class C;end

=> nil

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

···

On 7/23/07, F. Senault <fred@lacave.net> wrote:

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

···

On 7/24/07, F. Senault <fred@lacave.net> wrote:

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"

> cat test.rb
#! /usr/local/bin/ruby
class Test
  def self.inherited(c)
    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 ?

--
I always knew that one day Smalltalk would replace Java.
I just didn't know it would be called Ruby
-- Kent Beck

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

···

On 7/24/07, Todd Benson <caduceass@gmail.com> wrote:

On 7/23/07, F. Senault <fred@lacave.net> wrote:
> 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"
>
> > cat test.rb
> #! /usr/local/bin/ruby
> class Test
> def self.inherited(c)
> 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 :
> lacave.net )
>
> 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,

> class A; def self.inherited(s); puts "#{s}!"; end; end
=> nil
> class B < A; end
B!
=> nil
> class B < A; end
=> nil
> class C;end
=> nil
> 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

--
I always knew that one day Smalltalk would replace Java.
I just didn't know it would be called Ruby
-- Kent Beck

The full code is there :

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(c)
          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

···

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

On 7/24/07, F. Senault <fred@lacave.net> 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?

--
Au bout De la course Remonte jusqu'a A la source One trip One noise
Circuit Nuit bleue Spécialiste De l'enjeu One trip One noise
Longue attente avant de s'elancer One trip (Noir Désir,
Longue vie et tout a recracher One noise One Trip / One Noise)

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

···

Le 24 juillet 2007 à 01:25, Todd Benson a écrit :
--
Go not to Usenet for counsel, for they will say both 'No' and 'Yes' and
'Try another newsgroup'.

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

lacave.net

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.

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 :

···

On 7/24/07, F. Senault <fred@lacave.net> wrote:

Le 24 juillet 2007 à 10:02, Robert Dober a écrit :
> On 7/24/07, F. Senault <fred@lacave.net> wrote:

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(c)
          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
--
Au bout De la course Remonte jusqu'a A la source One trip One noise
Circuit Nuit bleue Spécialiste De l'enjeu One trip One noise
Longue attente avant de s'elancer One trip (Noir Désir,
Longue vie et tout a recracher One noise One Trip / One Noise)

--
I always knew that one day Smalltalk would replace Java.
I just didn't know it would be called Ruby
-- Kent Beck

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

···

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:

------------------------------------------------------------------------
      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 Gray II

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

···

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

On 7/24/07, F. Senault <fred@lacave.net> 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 :(.

--
I feel it move across my skin. I'm reaching up and reaching out, I'm
reaching for the random or what ever will bewilder me. And following
our will and wind we may just go where no one's been. We'll ride the
spiral to the end and may just go where no one's been. (Tool, Lateralus)

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

···

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

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

--
A groan of tedium escapes me, startling the fearful. Is this a test ?
It has to be, otherwise I can't go on. Draining patience, drain
vitality. This paranoid, paralyzed vampire act's a little old. But I'm
still right here, giving blood, keeping faith (Tool, The Patient)

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

···

On 7/24/07, F. Senault <fred@lacave.net> wrote:

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

> On 7/24/07, F. Senault <fred@lacave.net> 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 :(.

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

--
I always knew that one day Smalltalk would replace Java.
I just didn't know it would be called Ruby
-- Kent Beck

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 :

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 Gray II

···

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

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

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

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

Todd

···

On 7/24/07, James Edward Gray II <james@grayproductions.net> wrote:

>> 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 Gray II

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(c)
      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

···

Le 24 juillet à 16:45, James Edward Gray 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.

--
The problem with defending the purity of the English language is that
English is about as pure as a cribhouse whore. We don't just borrow
words; on occasion, English has pursued other languages down alleyways
to beat them unconscious and rifle their pockets for new vocabulary.
                                                (James D. Nicoll, rasfw)

For the curious / interested :

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

Fred

···

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

--
Drove the night toward my home
The place that I was born, on the lakeside
As daylight broke, I saw the earth
The trees had burned down to the ground (Peter Gabriel, Don't Give Up)