Inheritance of class variables

  • Thread starter Eustaquio Rangel de Oliveira Jr.
  • Start date
E

Eustaquio Rangel de Oliveira Jr.

Hello there.

I was talking with chris2 and matju on the irc channel and they gave me
good explanation about this point, but I'd like to share it with you and
discuss something about it.

I was asking about if, when defined on a parent class, a class variable
overwrites the class variables with the same name on it's children.

For example:

------------------------------------------------------------------------
class CarBuilder
@@total_of_cars=0
def initialize(name)
@name = name
end
def build
puts "#{@name} building another car ..."
@@total_of_cars +=1
end
def total_of_cars
"#{@name} built #{@@total_of_cars} cars"
end
end

class Honda < CarBuilder
@@total_of_cars=0
def initialize
super("Honda")
end
end

class Ford < CarBuilder
@@total_of_cars=0
def initialize
super("Ford")
end
end

h = Honda::new
f = Ford::new

puts h.total_of_cars
puts f.total_of_cars

h.build
h.build
h.build
f.build

puts h.total_of_cars
puts f.total_of_cars
------------------------------------------------------------------------

Running this code we have:

------------------------------------------------------------------------
Honda built 0 cars
Ford built 0 cars
Honda building another car ...
Honda building another car ...
Honda building another car ...
Ford building another car ...
Honda built 4 cars
Ford built 4 cars
------------------------------------------------------------------------

So, there is a mistake here. Honda built 3 cars and Ford 1, and not 4 for
Honda and 4 for Ford. They are sharing the parent class variable, not
matter if it was defined on the children also.

Matsu told me that who came first is the boss, so if @@total_of_cars was
defined on the parent, not matter what, all the other children will refer
to it. I can live with that making some hacks ehehe but let me see:

------------------------------------------------------------------------
class CarBuilder
def initialize(name)
@name = name
end
def build
puts "#{@name} building another car ..."
increase
end
def increase
end
end

class Honda < CarBuilder
@@total_of_cars=0
def initialize
super("Honda")
end
def increase
@@total_of_cars +=1
end
def total_of_cars
"#{@name} built #{@@total_of_cars} cars"
end
end

class Ford < CarBuilder
@@total_of_cars=0
def initialize
super("Ford")
end
def increase
@@total_of_cars +=1
end
def total_of_cars
"#{@name} built #{@@total_of_cars} cars"
end
end

h = Honda::new
f = Ford::new

puts h.total_of_cars
puts f.total_of_cars

h.build
h.build
h.build
f.build

puts h.total_of_cars
puts f.total_of_cars
------------------------------------------------------------------------

Running it I got:

------------------------------------------------------------------------
Honda built 0 cars
Ford built 0 cars
Honda building another car ...
Honda building another car ...
Honda building another car ...
Ford building another car ...
Honda built 3 cars
Ford built 1 cars
------------------------------------------------------------------------

That is the correct behaviour.
But let check what I changed there on the code:

- I removed @@total_of_cars from CarBuilder, and put it on *each* child
class (there was one row of code, now there is two - or more, if more
children), and could be a problem if I miss the point on some child class
and forget to declare that variable there).

- On the CarBuilder, I create an empty method there called "increase" just
to call it on the "build" method (that I want the children inherit it's
funcionality). Two more rows there.

- I also removed the total_of_cars method from CarBuilder, but needed to
write it on each children. 3 rows less on the parent, but 3 more on each child.

- Also on the children I needed to write the increase method, 3 more rows
on each child.

Sorry if I'm missing some point here, because I'm a Ruby newbie, but is
that correct? I mean, seems that we need to write more code with this kind
of behaviour.

And if we forget to implement some of the needed methods or the
@@total_of_cars on each CarBuilder child (CarBuilder could have a lot of
other methods that I could want to inherit also!), it could not work normally.

On another point, if all this methods and variables could be inherited from
the parent class, will be no problem.

I don't really know if there is a strong and well-defined OOP concept on
the fact that a child cannot overwrite it's parent class variables, but
could you

- Tell me if there is some kind of OOP concept like that and
- Tell me if the way I made the code, even with more lines, is the correct
and more efficient way to control how many cars each CarBuilder child made?

Thanks for your attention. :)
 
D

David Heinemeier Hansson

Sorry if I'm missing some point here, because I'm a Ruby newbie, but
is that correct? I mean, seems that we need to write more code with
this kind of behaviour.

I was quite puzzled by this behavior too. So in Rails I use a module
called class_inheritable_attributes.rb. It's a bit inconvenient, but I
usually wrap all calls to this lib in pretty accessors anyway, so it's
not that big of a deal. And very helpful :)

http://dev.rubyonrails.com/file/trunk/activesupport/lib/
class_inheritable_attributes.rb
--
David Heinemeier Hansson,
http://www.basecamphq.com/ -- Web-based Project Management
http://www.rubyonrails.org/ -- Web-application framework for Ruby
http://macromates.com/ -- TextMate: Code and markup editor (OS X)
http://www.loudthinking.com/ -- Broadcasting Brain
 
T

ts

E> Sorry if I'm missing some point here, because I'm a Ruby newbie, but is
E> that correct? I mean, seems that we need to write more code with this kind
E> of behaviour.

it's strange your model :)))

class CarBuilder
def self.inherited(kl)
kl.instance_eval { @total_of_cars = 0 }
super
end

def build
puts "#{self.class} building another car ..."
self.class.instance_eval { @total_of_cars += 1 }
end

def total_of_cars
"#{self.class} built #{self.class.instance_eval { @total_of_cars }} cars"
end
end

class Honda < CarBuilder
end

class Ford < CarBuilder
end



Guy Decoux
 
E

Eustaquio Rangel de Oliveira Jr.

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

| it's strange your model :)))

Hey Guy!

Thanks, it worked, but too much for a newbie, I need to take a look and
learn with this now. You scared me ahahah, I told you I was a newbie. :)
I didn't know that there was "self" on Ruby, for example. :)
And what about that instance_eval and inherited there? Ahhh. :)

Thanks! :)

|
| class CarBuilder
| def self.inherited(kl)
| kl.instance_eval { @total_of_cars = 0 }
| super
| end
|
| def build
| puts "#{self.class} building another car ..."
| self.class.instance_eval { @total_of_cars += 1 }
| end
|
| def total_of_cars
| "#{self.class} built #{self.class.instance_eval { @total_of_cars
}} cars"
| end
| end
|
| class Honda < CarBuilder
| end
|
| class Ford < CarBuilder
| end

- ----------------------------
Eustáquio "TaQ" Rangel
(e-mail address removed)
http://beam.to/taq
Usuário GNU/Linux no. 224050
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.2.6 (GNU/Linux)
Comment: Using GnuPG with Thunderbird - http://enigmail.mozdev.org

iD8DBQFB1Aokb6UiZnhJiLsRAs0oAKC2uZAh8MB2amkV96JtIqa3vgQ6xACfUn3Y
EV+7wuTrWDyD8tq1LxBaV9w=
=DDYu
-----END PGP SIGNATURE-----
 
S

Shashank Date

Eustaquio said:
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

| it's strange your model :)))

Hey Guy!

Thanks, it worked, but too much for a newbie, I need to take a look and
learn with this now. You scared me ahahah, I told you I was a newbie. :)

No, you scared not only becuase you are newbie, but because you were
answered by Guy :)

Keep working on Ruby for many more years and he will still scare you ;-)
I didn't know that there was "self" on Ruby, for example. :)
And what about that instance_eval and inherited there? Ahhh. :)

Thanks! :)

Have fun !
-- shanko
 
E

Eustaquio Rangel de Oliveira Jr.

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Hi!

|> No, you scared not only becuase you are newbie, but because you were
|> answered by Guy :)
|> Keep working on Ruby for many more years and he will still scare you ;-)

So he's kind of a Ruby boogeyman??? :-D
Just kidding. :-D
Thanks for your help (my brain is still hot here).

- ----------------------------
Eustáquio "TaQ" Rangel
(e-mail address removed)
http://beam.to/taq
Usuário GNU/Linux no. 224050
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.2.6 (GNU/Linux)
Comment: Using GnuPG with Thunderbird - http://enigmail.mozdev.org

iD8DBQFB1BE2b6UiZnhJiLsRAtqXAKCHE/FyFRVNT4tnq8PSIK5GQnpImwCgsNUG
c6lJaB/sETEPKkIe00VbwBU=
=amdM
-----END PGP SIGNATURE-----
 
R

Ruth A. Kramer

Eustaquio said:
Thanks for your help (my brain is still hot here).

My brain is not here either (I'm fighting a headache) and I didn't
carefully read the example code, but maybe the following will help (and
maybe it's way off base).

Ruby allows you to have class variables and instance variables. If you
have a class like cars (I didn't read the example closely at all) and
instances for Ford, Honda, ..., only one class variable will exist but
instance variables will exist for Ford, Honda, .... Thus, if, for
example, you want a total over all cars, you (with suitable coding) want
to use the class variable. If you want separate totals for Ford, Honda,
..., you want to use instance variables.

Going even further out on a limb (I'm a Ruby newbie too (and <sad
attempt at a joke warning>and if you don't like my ideas, you could say
goodbye, Ruby newbie, and even write a song about it</sad attempt at a
joke warning>)), IIRC, a class variable is prefixed with @@, and
something else (maybe instance variables?) are prefixed with @.

Randy Kramer
 
E

Eustaquio Rangel de Oliveira Jr.

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Hi!

| Going even further out on a limb (I'm a Ruby newbie too (and <sad
| attempt at a joke warning>and if you don't like my ideas, you could say
| goodbye, Ruby newbie, and even write a song about it</sad attempt at a
| joke warning>)), IIRC, a class variable is prefixed with @@, and
| something else (maybe instance variables?) are prefixed with @.

No need to say goodbye or write a song ahahah. :)
After all, I play on a Thrash Metal band, and we'are not so good on sad
songs ehehe. :)

Best regards,

- ----------------------------
Eustáquio "TaQ" Rangel
(e-mail address removed)
http://beam.to/taq
Usuário GNU/Linux no. 224050
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.2.6 (GNU/Linux)
Comment: Using GnuPG with Thunderbird - http://enigmail.mozdev.org

iD8DBQFB1BhDb6UiZnhJiLsRAmchAKCOfldehdbOugM2nwVmQ1xgKHZVWACfZwzi
12/52V+nKZ6VVx4G5+5zX8Y=
=Uv2S
-----END PGP SIGNATURE-----
 
R

Robert Klemme

Eustaquio Rangel de Oliveira Jr. said:
Hello there.

I was talking with chris2 and matju on the irc channel and they gave me
good explanation about this point, but I'd like to share it with you and
discuss something about it.

I was asking about if, when defined on a parent class, a class variable
overwrites the class variables with the same name on it's children.

The short answer is the one that David gave implicite: just don't use
them. IMHO you can safely ignore this language feature without loosing
much but gaining a lot (understandability of code etc.).

As Guy pointed out, you should really be counting the number of cars in
the individual car classes. That's what he's doing with his instance
variable accesses.

I would probably model your example completely different, because Honda
and Ford are really instances of CarBuilder not really sub classes. So
this is the result and suddenly we don't need class vars any more:

class CarBuilder
attr_reader :name, :total_of_cars

def initialize(name)
@name = name
@total_of_cars = 0
end

def build
puts "#{name} builds another car"
@total_of_cars += 1
end
end

h = CarBuilder.new "Honda"
f = CarBuilder.new "Ford"

h.total_of_cars
f.total_of_cars

h.build
h.build
h.build
f.build

h.total_of_cars
f.total_of_cars


?> h = CarBuilder.new "Honda"
?> h.total_of_cars
=> 0?> h.build
Honda builds another car
=> 1Honda builds another car
=> 2Honda builds another car
=> 3Ford builds another car
=> 1?> h.total_of_cars
=> 3=> 1

Regards

robert
 
E

Eustaquio Rangel de Oliveira Jr.

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Hi!

| I would probably model your example completely different, because Honda
| and Ford are really instances of CarBuilder not really sub classes. So
| this is the result and suddenly we don't need class vars any more:

Thanks for your example! I'm learning about Ruby and taking notes to make a
begginer's tutorial, so while the Guy example really help me to go deep
inside the Ruby depths and think about all that stuff there (wow Guy I was
really amazed with the concept of creating a instance variable there!),
your example is easier for newbies (like me, also) to understand.

Thanks!

- ----------------------------
Eustáquio "TaQ" Rangel
(e-mail address removed)
http://beam.to/taq
Usuário GNU/Linux no. 224050
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.2.6 (GNU/Linux)
Comment: Using GnuPG with Thunderbird - http://enigmail.mozdev.org

iD8DBQFB1Domb6UiZnhJiLsRAueNAJ9kh49S5orefWnjnGzUFApTTSf5fgCgso88
qCqJuWGtyKasJRhWZb0+154=
=9xCK
-----END PGP SIGNATURE-----
 
R

Robert Klemme

Eustaquio Rangel de Oliveira Jr. said:
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Hi!

| I would probably model your example completely different, because Honda
| and Ford are really instances of CarBuilder not really sub classes. So
| this is the result and suddenly we don't need class vars any more:

Thanks for your example! I'm learning about Ruby and taking notes to make
a
begginer's tutorial, so while the Guy example really help me to go deep
inside the Ruby depths and think about all that stuff there (wow Guy I was
really amazed with the concept of creating a instance variable there!),
your example is easier for newbies (like me, also) to understand.

But please note that my point was not so much about Ruby but about OO
modeling. Honda, Ford and others are really instances of CarBuilder.
That's why you just need one class.

Of course, if you had different kinds of CarBuilders with different
capabilities (say LorryCarBuilder, SedanCarbuilder, SUVCarBuilder and
SportsWagonCarBuilder) you would need several classes; but even then Ford
would be an instance of LorryCarBuilder (or whatever) which normally would
inherit CarBuilder.

You're welcome! I'm glad I could be of any help.

Regards

robert
 
D

darren

I think I understand all of the code in this thread (see below) except for the
2nd line "attr_reader :name, :total_of_cars". Could you, or someone, explain
this line to me, please?

Also, I tried putting this code into a single *.rb file and I couldn't get any
output. I did get a few warnings, however:

ex.
/1foo.rb:4: warning: parenthesize argument(s) for future version
/1foo.rb:7: warning: parenthesize argument(s) for future version
/1foo.rb:8: warning: parenthesize argument(s) for future version
/1foo.rb:12: warning: parenthesize argument(s) for future version
/1foo.rb:12: warning: parenthesize argument(s) for future version
/1foo.rb:13: warning: parenthesize argument(s) for future version
/1foo.rb:4: undefined method ` ' for CarBuilder:Class (NoMethodError)

I guess the warnings mean that some syntax is being deprecated in later
versions. Assuming that is correct, I still don't see what is generating the
last error.

So, I tried using irb to enter it line by line. However, when I get down to
the where I declare the first instance (h = CarBuilder.new "Honda"), irb
gives me the error, "NameError: uninitialized constant CarBuilder".

Finally, I don't see where you are keeping up with the actual total of cars
built by all manufacturers. So, for practice, I was going to try to extend
this code to handle that. Would the proper way to do that be to declare a
class variable (like @@collective_total = 0) and put it somewhere inside of
the class (like right after the attr_reader . . . line)? Then, inside of the
build method, bump the collective_total?

Thanks,
Darren
 
R

Robert Klemme

darren said:
I think I understand all of the code in this thread (see below) except for
the
2nd line "attr_reader :name, :total_of_cars". Could you, or someone,
explain
this line to me, please?

Also, I tried putting this code into a single *.rb file and I couldn't get
any
output. I did get a few warnings, however:

ex.
/1foo.rb:4: warning: parenthesize argument(s) for future version
/1foo.rb:7: warning: parenthesize argument(s) for future version
/1foo.rb:8: warning: parenthesize argument(s) for future version
/1foo.rb:12: warning: parenthesize argument(s) for future version
/1foo.rb:12: warning: parenthesize argument(s) for future version
/1foo.rb:13: warning: parenthesize argument(s) for future version
/1foo.rb:4: undefined method ` ' for CarBuilder:Class (NoMethodError)

I guess the warnings mean that some syntax is being deprecated in later
versions. Assuming that is correct, I still don't see what is generating
the
last error.

Which version of Ruby are you using? I tested with 1.8.1 and didn't see any
of those errors. Maybe it's a copy and paste problem from the mail? Line
endings could be a reason.
So, I tried using irb to enter it line by line. However, when I get down
to
the where I declare the first instance (h = CarBuilder.new "Honda"), irb
gives me the error, "NameError: uninitialized constant CarBuilder".

Works for me:

$ irbs?> h = CarBuilder.new "Honda"
=> # said:
Finally, I don't see where you are keeping up with the actual total of
cars
built by all manufacturers.

My example code doesn't.
So, for practice, I was going to try to extend
this code to handle that. Would the proper way to do that be to declare a
class variable (like @@collective_total = 0) and put it somewhere inside
of
the class (like right after the attr_reader . . . line)? Then, inside of
the
build method, bump the collective_total?

I would not use a class var. Rather use an instance var of the class.

You could also query all instances which reduces redundancy and thus error
likelyness:
Honda builds another car
=> 1Honda builds another car
=> 2Honda builds another car
=> 3=> 3

Of course, if you need the number of all cars often then it's more
efficient to introduce redundancy and have a total of all car makers
available.

Regards

robert
 
D

darren

Which version of Ruby are you using? I tested with 1.8.1 and didn't see
any of those errors. Maybe it's a copy and paste problem from the mail?
Line endings could be a reason.

I'm using 1.8.1, too. I just retyped it in and it's working now. Guess it
Must have been a hidden EOL of or something.

I just ran across the explanation for att_reader in my Ruby book. Cool
shortcut.

Thanks,
Darren
 
J

Jeffrey Moss

I remember seeing this topic posted to the list a while ago. I'm trying to
make a similarly complex model, and its not clear if it is going to work, or
if there is a simpler solution for that matter. For the car example I agree
that passing the model of the car into the constructor makes sense, but here
is my example.

I have a class "ActionSequence" for use with rails that has functions "next"
and "previous", I want to pass into the constructor the current action and
controller, so this is what ActionSequence looks like:

class ActionSequence
def initialize(action, controller)
@action = action
@controller = controller
end

def next
action = sequence[ sequence.index(@action) + 1 ]
process_action(action)
end

def previous
action = sequence[ sequence.index(@action) - 1 ]
process_action(action)
end

protected
def sequence
self.class.instance_eval { @sequence }
end

private
def process_action(action)
if self.respond_to?(action, true)
return self.send(action.id)
else
return action
end
end
end

And an example of a typical usable child class is as follows:

class OrderSequence < ActionSequence
@@sequence = ['order_selection',
'account_setup',
'personal_information']
def order_selection
"error_page"
end
end

So before anything is done, the action will initialize the sequence to the
right spot, and inside my "view" I can call next and previous as a helper
and it refers to OrderSequence.next and OrderSequence.previous, which
traverses the @@sequence class variable, and it will check for a callback
function for additional logic.

But this doens't work. I haven't been able to figure very much out about how
instance_eval works on Class objects. If anybody can offer any advise, or if
anybody has done a similar sort of thing, I'd appreciate some input.

-Jeff

----- Original Message -----
From: "Robert Klemme" <[email protected]>
Newsgroups: comp.lang.ruby
To: "ruby-talk ML" <[email protected]>
Sent: Thursday, December 30, 2004 10:16 AM
Subject: Re: Inheritance of class variables
 
T

ts

You probably make confusion between class variable and class instance
variable

J> def sequence
J> self.class.instance_eval { @sequence }

this is a class instance variable.

J> class OrderSequence < ActionSequence
J> @@sequence = ['order_selection',
J> 'account_setup',
J> 'personal_information']

this is a class variable.

Try it with

class OrderSequence < ActionSequence
@sequence = ['order_selection',
'account_setup',
'personal_information']

def order_selection
"error_page"
end
end


Guy Decoux
 
C

(Curne) Simon Conrad-Armes

Hi all,

Personally I never use class variables, since I consider them
inherently broken. Although I have no constructive opinion of how they
_should_ function, I know after trying several times, that they simply
do not work the way I want to use them. Instead I sometimes use class
_instance_ variables, that is instance variables on the class object.
The idea is exactly the same as Guy's, but with lazy initialization, so
I get to skip some of the instance_evals. Thus my version of the code
would look like...

------------------------------------------------------------------------
class CarBuilder
def initialize(name)
@name = name
end

def self.total_of_cars
@total_of_cars or 0
end

def self.increase_total
@total_of_cars = (@total_of_cars or 0) + 1
end

def build
puts "#{@name} building another car ..."
self.class.increase_total
end

def total_of_cars
"#{@name} built #{self.class.total_of_cars} cars"
end
end

class Honda < CarBuilder
def initialize
super("Honda")
end
end

class Ford < CarBuilder
def initialize
super("Ford")
end
end

h = Honda.new
f = Ford.new

puts h.total_of_cars
puts f.total_of_cars

h.build
h.build
h.build
f.build

puts h.total_of_cars
puts f.total_of_cars
------------------------------------------------------------------------


The output then shows 3 Hondas and 1 Ford. Of course, this is a simple
special case of a factory object, the class object being the factory.
Thus I would probably write the code slightly different, putting the
#total_of_cars method on the class and making #build a synonym of #new,
which would seem to me the saner approach.

Just my two euro-cents.
/Curne
 

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

No members online now.

Forum statistics

Threads
474,164
Messages
2,570,898
Members
47,439
Latest member
shasuze

Latest Threads

Top