Forum: Ruby Inherited & load

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.
B57c5af36f5c1f33243dd8b2dd9043b1?d=identicon&s=25 F. Senault (Guest)
on 2007-07-24 00:01
(Received via mailing list)
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 :
http://www.lacave.net/~fred/projets/plugin.rb )

Fred
289cf19aa581c445915c072bf45c5e25?d=identicon&s=25 Todd Benson (Guest)
on 2007-07-24 01:26
(Received via mailing list)
On 7/23/07, F. Senault <fred@lacave.net> 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
703fbc991fd63e0e1db54dca9ea31b53?d=identicon&s=25 Robert Dober (Guest)
on 2007-07-24 10:04
(Received via mailing list)
On 7/24/07, F. Senault <fred@lacave.net> 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 ;) for what
you want to achieve?
Maybe you could tell us a little bit more about that?

Cheers
Robert
703fbc991fd63e0e1db54dca9ea31b53?d=identicon&s=25 Robert Dober (Guest)
on 2007-07-24 10:40
(Received via mailing list)
On 7/24/07, Todd Benson <caduceass@gmail.com> 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
B57c5af36f5c1f33243dd8b2dd9043b1?d=identicon&s=25 F. Senault (Guest)
on 2007-07-24 11:50
(Received via mailing list)
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 ;) 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...  :)

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

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

Fred
B57c5af36f5c1f33243dd8b2dd9043b1?d=identicon&s=25 F. Senault (Guest)
on 2007-07-24 12:11
(Received via mailing list)
Le 24 juillet 2007 à 01:25, Todd Benson a écrit :

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

Good !  :)

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

Fred
703fbc991fd63e0e1db54dca9ea31b53?d=identicon&s=25 Robert Dober (Guest)
on 2007-07-24 12:22
(Received via mailing list)
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 :(.

Cheers
Robert
Then, I can use, for instance :
B57c5af36f5c1f33243dd8b2dd9043b1?d=identicon&s=25 F. Senault (Guest)
on 2007-07-24 12:50
(Received via mailing list)
Le 24 juillet 2007 à 12:20, Robert Dober a écrit :

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

Fred
703fbc991fd63e0e1db54dca9ea31b53?d=identicon&s=25 Robert Dober (Guest)
on 2007-07-24 14:01
(Received via mailing list)
On 7/24/07, F. Senault <fred@lacave.net> 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
4299e35bacef054df40583da2d51edea?d=identicon&s=25 James Gray (bbazzarrakk)
on 2007-07-24 15:19
(Received via mailing list)
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.  :|

$ 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 Gray II
B57c5af36f5c1f33243dd8b2dd9043b1?d=identicon&s=25 F. Senault (Guest)
on 2007-07-24 16:41
(Received via mailing list)
Le 24 juillet à 15:18, James Edward Gray 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.  :|
>
> $ 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
4299e35bacef054df40583da2d51edea?d=identicon&s=25 James Gray (bbazzarrakk)
on 2007-07-24 16:48
(Received via mailing list)
On Jul 24, 2007, at 9:40 AM, F. Senault wrote:

> Alas, Object is not a module :
Did you ask it?  ;)

 >> 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
289cf19aa581c445915c072bf45c5e25?d=identicon&s=25 Todd Benson (Guest)
on 2007-07-24 16:54
(Received via mailing list)
On 7/24/07, James Edward Gray II <james@grayproductions.net> wrote:

> => []
>
> Hope that helps.
>
> James Edward Gray II
>

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

Todd
B57c5af36f5c1f33243dd8b2dd9043b1?d=identicon&s=25 F. Senault (Guest)
on 2007-07-24 17:15
(Received via mailing list)
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.

Thanks a lot for the migraine... I guess...  :)

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
B57c5af36f5c1f33243dd8b2dd9043b1?d=identicon&s=25 F. Senault (Guest)
on 2007-07-24 21:21
(Received via mailing list)
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
This topic is locked and can not be replied to.