Proper way to define a subclass within parent file?

S

Scott Strattner

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)
 
S

Stefano Crocco

Alle Tuesday 28 October 2008, Scott Strattner ha scritto:
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.

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
 
S

Scott Strattner

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

Brian Candler

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

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,968
Messages
2,570,153
Members
46,699
Latest member
AnneRosen

Latest Threads

Top