Forum: Ruby Proper way to define a subclass within parent file?

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.
C0a0d546f0e23a6ba2b9cb5b2133a2fe?d=identicon&s=25 Scott Strattner (strattner)
on 2008-10-28 18:57
I am starting to build a script to manipulate various network device
information, including a list of Vlans from a switch. To that end, I
created a Vlan class:

class Vlan
  @@max = 399
  @@min = 1
  @@protected = [1,2,5]
  @@namelen = 32
  def initialize (id,desc)
    self.set_name(desc) if (self.set_id(id))
  end
  def set_id (id)
    return nil unless (id.kind_of? Integer)
    return nil if (@@protected.index(id) || id > @@max || id < @@min)
    @id = id
  end
  def set_name (desc)
    desc.gsub!(/\s+/,"_")
      desc = desc[0..@@namelen-1] if (desc.length > @@namelen)
      @description = desc
  end
  def to_s
    return "#{@id}\t#{@description}"
  end
  def valid?
    return true if (@id.kind_of? Integer)
    return false
  end
  # snip various other methods
end

This works as expected; to test it out I added some debugging code to
loop through some values and verify it only created valid instances:

v = Array.new
20.times { |a|
  temp = Vlan.new(a,"this is vlan id #{a}")
  v.insert(-1,temp) if (temp.valid?)
}
v.length.times { |a| puts "#{a} #{v[a].to_s}" }

I then added a simple subclass, within the same file. This special kind
of Vlan acts like the others, except it uses a different range of valid
id values:

class HMC_Vlan < Vlan
  @@min = 701
  @@max = 799
  @@protected = [700,705]
end

I then added to the debug code:

v2 = Array.new
700.upto(720) { |a|
  temp2 = HMC_Vlan.new(a,"hmcvlan.#{a}")
  v2.insert(-1,temp2) if (temp2.valid?)
}
v2.length.times { |a| puts "#{a} #{v2[a].to_s}" }

When I ran this file, it created HMC_Vlans as expected, but no longer
created any valid Vlans ("v" array was empty). It turns out the class
variables @@max, min, protected in HMC_Vlan are overwriting the class
variables in Vlan - even though no instance of HMC_Vlan is created until
after my debug code creates the "v" array.
It was my understanding that a Class declaration was treated as a
conditional block - *if* someone creates this Class, *then* run through
the contents. But here it appears the class declaration is processed
before any instance of it is made. Even so, shouldn't a class variable
be specific to that class, and not be shared with children? Is there
another kind of inheritance I can use to maintain this separation in
class variables? Is there a specific way to define a subclass within the
same file as the parent? Why does the class HMC_Vlan definition get
executed before any instances are created?

I am running this under Windows (ActiveRuby 1.8.6)
C40020a47c6b625af6422b5b1302abaf?d=identicon&s=25 Stefano Crocco (crocco)
on 2008-10-28 20:32
(Received via mailing list)
Alle Tuesday 28 October 2008, Scott Strattner ha scritto:
>     self.set_name(desc) if (self.set_id(id))
>   end
> This works as expected; to test it out I added some debugging code to
> of Vlan acts like the others, except it uses a different range of valid
> v2 = Array.new
> after my debug code creates the "v" array.
> It was my understanding that a Class declaration was treated as a
> conditional block - *if* someone creates this Class, *then* run through
> the contents.

You're wrong here. The body of a class definition is executed when it's
found.
Method bodies (for both instance methods and class methods) aren't
executed
untill they're called, but all the rest of the class body is executed
immediately. For example, the code

class C

  puts "body of class C"

  def method1
    puts "body of method method1"
  end

end

when read produces the output

"body of class C"

and, after that, calling C.instance_methods will return an array which
contains the string "method1", showing that the method method1 has
already
been defined. On the other hand, the body of method1 is only executed
after a
call to it, for example with the code C.new.method1.

> But here it appears the class declaration is processed
> before any instance of it is made. Even so, shouldn't a class variable
> be specific to that class, and not be shared with children? Is there
> another kind of inheritance I can use to maintain this separation in
> class variables? Is there a specific way to define a subclass within the
> same file as the parent?

Class variables are shared among a class and all its subclasses. If you
don't
want that, you can use class instance variables, that is instance
variables of
the class object. They work like common instance variables, and can be
accessed outside the class body only if appropriate class methods are
defined
(note: this is also true inside instance methods of the class). You
could do
this:

 class Vlan
   @max = 399
   @min = 1
   @protected = [1,2,5]
   @namelen = 32

   def self.max
     @max
   end

   def self.min
     @min
   end

   def self.protected
    @protected
   end

   def set_id (id)
     return nil unless (id.kind_of? Integer)
     return nil if (self.class.protected.index(id) || id >
self.classl.max ||
id < self.class.min)
     @id = id
   end

...

The only problem with this approach is that all instance variables
should be
initialized in all classes which need them (you don't need to redefine
the
methods, instead).

Another possibility, if the values in the instance variables shouldn't
change
is to use constants:

class Vlan
 MAX = 399
 MIN = 1
 PROTECTED = [1,2,5]
 NAMELEN = 32

I hope this helps

Stefano
C0a0d546f0e23a6ba2b9cb5b2133a2fe?d=identicon&s=25 Scott Strattner (strattner)
on 2008-10-28 21:09
Stefano Crocco wrote:


> Class variables are shared among a class and all its subclasses. If you
> don't
> want that, you can use class instance variables, that is instance
> variables of
> the class object.

I knew of instance variables, and class variables, but not class
instance variables. It seems to do what I need, although the syntax will
take some getting used to.

> Another possibility, if the values in the instance variables shouldn't
> change
> is to use constants:

I originally had methods to modify these class variables, but I just
tried using constants, and that worked. So I will probably create a
subclass for every kind of VLAN encountered (which will then define the
constant values for that particular subclass).

> I hope this helps

It did. Thanks.
753dcb78b3a3651127665da4bed3c782?d=identicon&s=25 Brian Candler (candlerb)
on 2008-10-28 21:32
Scott Strattner wrote:
>> Another possibility, if the values in the instance variables shouldn't
>> change
>> is to use constants:
>
> I originally had methods to modify these class variables, but I just
> tried using constants, and that worked. So I will probably create a
> subclass for every kind of VLAN encountered (which will then define the
> constant values for that particular subclass).

Note: when referring to constant Foo, use self.class::Foo in a subclass
if you want dynamic lookup, otherwise Foo will statically be resolved to
the one in the parent.

class Bar
  Foo = 1
  def test1
    Foo               # always refers to Bar::Foo, even in a subclass
  end
  def test2
    self.class::Foo
  end
end

class Baz < Bar
  Foo = 2
end

p Bar.new.test1 # prints 1
p Baz.new.test1 # prints 1
p Bar.new.test2 # prints 1
p Baz.new.test2 # prints 2
This topic is locked and can not be replied to.