Nested Classes Question

18 views
Skip to first unread message

Alex Jarvis

unread,
May 27, 2012, 3:44:37 PM5/27/12
to boston-r...@googlegroups.com
Hello, BRB. I am working out a problem for my Stattr gem and I could use some assistance. I'll make the abstract code here before I get into the specifics.

Lets say I have nested classes A and B, seen here: 

class A 
  
  def self.sides 
    2
  end 
  
  class B 
  
  def self.put_sides(a = A.sides) 
    puts "the sides are #{a}"
  end

end 

So, as expected, printing A::B.put_sides would return "the sides are 2". 

Now lets say I wanted to do this: 

class A2 < A
   def self.sides 
     50
    end 
end

Rightfully so, printing A2::B.put_sides would print "The sides are 2." is there any generic way I can change the line (a = A.sides) to refer to "the class from which this is nested?" so that A2::B.put_sides would return 50? 

 Or, perhaps a better way to solve this problem in general?  


Thanks! 





---------------------------
Alex M. Jarvis
www.AlxJrvs.com
'AlxJrvs' on Twitter

Brian Cardarella

unread,
May 27, 2012, 4:09:30 PM5/27/12
to boston-r...@googlegroups.com
You should rethink what you want to do, and without more context I
cannot tell you what direction to head in.

The issue here lies with a misunderstanding that Ruby doesn't actually
have "nested classes". It may seem to be declared that way but it is
not. For example (using your code):

> A::B
=> "A::B"

> A2::B
=> "A::B"

If Ruby actually had nested classes then the inheritance for A2 would
have also copied the B class, and it did not as inspecting that class
clearly shows it is still namespaced under "A".

You should, instead, explicitly pass the object that you want to call
that method from:

> A::B.put_sides(A)
=> "the sides are 2"

> A::B.put_sides(A2)
=> "the sides are 50"

- Brian
> --
> You received this message because you are subscribed to the Boston Ruby
> Group mailing list
> To post to this group, send email to boston-r...@googlegroups.com
> To unsubscribe from this group, send email to
> boston-rubygro...@googlegroups.com
> For more options, visit this group at
> http://groups.google.com/group/boston-rubygroup

Alex Jarvis

unread,
May 27, 2012, 4:26:26 PM5/27/12
to boston-r...@googlegroups.com
Thanks brian - I had guessed that this was a potential issue. I'll try to dig deeper to the root problem. 

I have this gem I have been kicking around for some time, found here. The intent was to create a series of tools to help people making Tabletop RPG programs (though, realistically, it is a way to hone my skills in my free time). 

My intention was to make it so that the person using the gem would have to re-write as little as possible. I have a class, Game, that contains several methods that return certain truths about the game at hand - so, self.dice_sides = 6 (the number of sides in a die used to generate the character), or stats (an array of the stats used in the game). this is referenced elsewhere, accordingly, as Game.stats, Game.dice_sides, etc. This is useful because - even as I transition away from including serious logic in the gem - the StatList class is always going to want a list of attr_accessors that is equal to the stats of the game, and it would be better to have the eventual user change one line rather than changing every instance of "Game" to the new class manually. 

When making new versions of games, I realized that simply overwriting these values was tricky; two games couldn't exist in the same database down the road, for example, because they would have different values for Game.dice_sides. The initial solution was to alias; if i had to overwrite something, it would be under this aliased name in lieu of 'Game' (so, Dnd.dice_sides) and I could re-write the Game class wholeheartedly with these new values... which brings me back to the same problem (if they are aliased, I am not sure it fixes the issue at hand.) 

My initial thought was to use "game" as a superclass (containing all the other classes) and then have the end user extend "Game" to "Pathfinder" or "DnD", and then overwriting those values as they will. I already tried to do this with Constants, but it appears to not work either. 

So, perhaps the root of my question: Is there something that will last within the scope of a class that can be referenced by nested classes that I can re-write when I extend the class? 

In psuedocode: 

class A
 (magic) = 6
  class B
    def b (b = (magic) ) 
      puts (magic) 
    end
end 

class A2 < A 
  (magic) =40 
end 

A2::B.b 
40  

(I understand that I just incorrectly used the 'nested classes' syntax again incorrectly, but it was the only way I knew how to get the idea across. Being told "This is a fruitless path to continue to tread" is useful advice as well, if there isn't a simple or good way to do this.) 


---------------------------
Alex M. Jarvis
www.AlxJrvs.com
'AlxJrvs' on Twitter



Brian Cardarella

unread,
May 27, 2012, 4:39:27 PM5/27/12
to boston-r...@googlegroups.com
Why not just write a module and extend into the A class? If you are only calling class level methods then you don't need to instantize your class and separating your methods into different classes seems unnecessary. But again, context matters and with the simple example you are giving it is difficult to say what is the best thing to do.

- Brian

Sent from my iPhone

Daniel Choi

unread,
May 27, 2012, 5:08:30 PM5/27/12
to boston-r...@googlegroups.com, dhc...@gmail.com
You can do this:

class Game
def report_sides
puts "The sides are #{@sides}"
end
end

class DnD < Game
def initialize
@sides = 50
end
end

class Pathfinder < Game
def initialize
@sides = 6
end
end

dnd = DnD.new

dnd.report_sides
# => The sides are 50

pf = Pathfinder.new

pf.report_sides
# => The sides are 6

Is this missing anything you need?

I think you may be conflating namespacing (e.g A::B and nesting class
B's definition within class A's definition) with inheritance. But I may
be wrong.

Dan


On Sun, May 27, 2012 at 04:26 PM, Alex Jarvis <alx...@gmail.com> wrote:

> from: Alex Jarvis <alx...@gmail.com>
> date: Sun, May 27 04:26 PM -04:00 2012
> to: boston-r...@googlegroups.com
> reply-to: boston-r...@googlegroups.com
> subject: Re: [boston.rb] Nested Classes Question
>
> Thanks brian - I had guessed that this was a potential issue. I'll try to
> dig deeper to the root problem.
>
> I have this gem I have been kicking around for some time, found
> here<https://github.com/alxjrvs/Stattr>.
----
Sent from vmail
https://github.com/danchoi/vmail

Ryan LeCompte

unread,
May 27, 2012, 5:11:55 PM5/27/12
to boston-r...@googlegroups.com, boston-r...@googlegroups.com, dhc...@gmail.com
I would also strive to come up with a design that doesn't rely on inheritance, but instead composition. Think about creating objects that are loosely coupled and have separate/well-defined external interfaces.

Ryan

Alex Jarvis

unread,
May 27, 2012, 5:21:17 PM5/27/12
to boston-r...@googlegroups.com
Your answer(s) have helped a great deal! I think the way to go about this is through the constant. 

module A 
FOO = 6
end

module B 
  include A 
  FOO = 8
end

A::FOO = 



---------------------------
Alex M. Jarvis
www.AlxJrvs.com
'AlxJrvs' on Twitter



Alex Jarvis

unread,
May 27, 2012, 5:25:26 PM5/27/12
to boston-r...@googlegroups.com
....A ha! 

That is probably a *significantly* better way to do it . 


One quick question - is there any reason they need be instance, as opposed to class, variables? 




---------------------------
Alex M. Jarvis
www.AlxJrvs.com
'AlxJrvs' on Twitter



Daniel Choi

unread,
May 27, 2012, 5:33:12 PM5/27/12
to boston-r...@googlegroups.com, dhc...@gmail.com
On Sun, May 27, 2012 at 05:25 PM, Alex Jarvis <alx...@gmail.com> wrote:

> One quick question - is there any reason they need be instance, as opposed
> to class, variables?

You can use class instance variables, like this


class Game
def self.report_sides
puts "The sides are #{@sides}"
end
end

class DnD < Game
@sides = 50
end

class Pathfinder < Game
@sides = 6
end

dnd = DnD.new

dnd.class.report_sides
# => The sides are 50

pf = Pathfinder.new
pf.class.report_sides
# => The sides are 6


Don't use class variables, or else you'll get this


class Game
@@sides = 0
def self.report_sides
puts "The sides are #{@@sides}"
end
end

class DnD < Game
@@sides = 50
end

class Pathfinder < Game
@@sides = 6
end

dnd = DnD.new

dnd.class.report_sides
# => The sides are 6 <== WRONG

pf = Pathfinder.new
pf.class.report_sides

Brian Cardarella

unread,
May 27, 2012, 5:34:26 PM5/27/12
to boston-r...@googlegroups.com
You use an instance of a class when you can to customize some of the
variables of the class. For example, you might have a 'Car' class, and
when you create a new 'Car' object you'll pass in some params:

nissan = Car.new(:make => 'Nissan', :model => 'Maxima', :year => 2010)
ford = Car.new(:make => 'Ford', :model => 'Explorer', :year => 2000)

If you were creating a new class for every car type out there it would
require quite a bit. The instantized objects allow you to save quite a
bit of time when otherwise you'd be repeating yourself.

- Brian

Maurício Linhares

unread,
May 27, 2012, 5:42:33 PM5/27/12
to boston-r...@googlegroups.com
A constant that changes value isn't a constant anymore, you should avoid this kind of usage as much as possible.

The way Daniel Choi implemented is probably the best one, create one specific class for each game (you could also make them singletons if you care about creating objects) and fill in their details on every subclass.

But this would only be needed if there is specific behavior on these subclasses, if all you need is generic data like the kind of dices, amount of players and stuff like that you can safely create instances of your Game object, fill it in with the data needed and name them with the gane name.

Also, dont be afraid to use inheritance were it makes sense, there are many valid cases when inheritance is the best solution.

Enviado via iPad

Alex Jarvis

unread,
May 27, 2012, 5:55:07 PM5/27/12
to boston-r...@googlegroups.com
Thanks for the help, guys. There is one snag, however: Will the value of @sides (in this instance) be counted for the class Game when Game itself is called? I would guess not.

Let me go further into this problem (and preface it by saying that I completely understand if it is a problem of design.) 

I have a class called StatList that generates setters/getters based on an array returned by Game.stats (in its current incarnation). This allows me to write: the following line: 

class StatList
  attr_accessor *Game.stats

(....rest of class) 

end 

The idea I had at the outside was that another game could just change the return of Game.stats *in* game itself (and in my later Module-baed change, do so as a constant) , and then StatList would generate the correct attr_accessor 's when making a new instance. It works currently (in my github repo), but I run into the problems of extending it later. It would appear that my Module solution allows you to do this easily, as the scope of Game stays within the module. I can easily write it so as to not include constants. 

Under your method, I believe that it will look for Game.stats and return whatever is *there*, ignoring any subclasses of it (and therefore the values set in those subclasses). This was the original problem I ran into. 

I am going to walk the line of this Module-based solution a bit - but is my design sense completely off here? Should i just write it so that, when referencing it in the future, the programmer has to manually enter all of the stats and individual game values themselves? 

 



---------------------------
Alex M. Jarvis
www.AlxJrvs.com
'AlxJrvs' on Twitter



Mauricio Linhares de Aragao Junior

unread,
May 27, 2012, 6:32:50 PM5/27/12
to boston-r...@googlegroups.com
I think the best solution for this is TDDing to victory.

Can you provide some sample code on how do you think this code is going to be used?

Enviado via iPhone

Alex Jarvis

unread,
May 27, 2012, 6:32:53 PM5/27/12
to boston-r...@googlegroups.com
Working on it now! Will report back. 



---------------------------
Alex M. Jarvis
www.AlxJrvs.com
'AlxJrvs' on Twitter



Daniel Choi

unread,
May 27, 2012, 6:37:34 PM5/27/12
to boston-r...@googlegroups.com, dhc...@gmail.com
On Sun, May 27, 2012 at 05:55 PM, Alex Jarvis <alx...@gmail.com> wrote:
>
> Thanks for the help, guys. There is one snag, however: Will the value of
> @sides (in this instance) be counted for the class Game when Game itself is
> called? I would guess not.

If you want the DnD class to redefine the value of @sides for the
superclass Game, you can just use class variables.

class Game
@@sides = 0
def self.report
puts @@sides
end
end

Game.report
# => 0

class DnD < Game
@@sides = 1
end

DnD.report
# => 1
Game.report
# => 1

class Pathfinder < Game
@@sides =2
end

DnD.report
# => 2
Pathfinder.report
# => 2
Game.report
# => 2

But obviously, this approach will cause problems if you want to have many
different types of Games played at the same time, in the same Ruby process.


> I am going to walk the line of this Module-based solution a bit - but is my
> design sense completely off here? Should i just write it so that, when
> referencing it in the future, the programmer has to manually enter all of
> the stats and individual game values themselves?

Go for it! That's how to learn.

Dan



Daniel Choi

unread,
May 27, 2012, 9:10:00 PM5/27/12
to boston-r...@googlegroups.com

Maurício Linhares de Aragão Junior, I'm suffering from you-have-a-much-cooler-name envy.

Maurício Linhares

unread,
May 27, 2012, 9:14:09 PM5/27/12
to boston-r...@googlegroups.com
Freaking iPhone using my full name :P

It isn´t nice when I´m signing a credit card bill on those touch
screens, trust me.

-
Maurício Linhares
http://techbot.me/ - http://twitter.com/#!/mauriciojr


On Sun, May 27, 2012 at 10:10 PM, Daniel Choi <dhc...@gmail.com> wrote:
>
> Maurício Linhares de Aragão Junior, I'm suffering from
> you-have-a-much-cooler-name envy.
>
>
Reply all
Reply to author
Forward
0 new messages