5

I am trying to keep count of instances of objects of a given class, inside the class that defines those objects.

First of all I am aware of code reflection and ObjectSpace.each_object, but I'd prefer not to use reflection and let the class itself be able to "look after" itself.

I looked around and all solutions I found seem to make use of @@class_variables in the class definition like, for example, the accepted answer to this question: How to get class instances in Ruby?

As I kept reading around though, I found that class variables in ruby can behave very bad in some situations... biggest reason being this:

A class variable defined at the top‐level of a program is inherited by all classes. It behaves like a global variable.

source & more info here: http://ruby.runpaint.org/variables#class

So, I tried to code a class that stores the number of its instantiated objects inside itself using a class instance variable instead of a class variable, and apparently it seems to work ok but since I am still learning about this "in-deep" language topics I'd like to ask you if the code I wrote is correct and/or makes sense:

class Pizza
  @orders = 0
  def self.new
    @orders += 1
  end
  def self.total_orders
    @orders
  end
end

new_pizza = Pizza.new #=> 1
special_pizza = Pizza.new #=> 2
fav_pizza = Pizza.new #=> 3

One of my doubts is that overriding Pizza.new method i "delete" some important funcionality of the original .new method ? (What does the original .new method provide?? How can i "inspect" into a method code using reflection?) What else am I doing wrong, or totally wrong in my approach and why ?

Thanks

edit: forgot to add this important bit:

As a way to better constrain the scope of things, I'd like the Pizza class to be able to count only at object instantiation time and not to have a setter method on its @instance class variable, which can be accessed at anytime in code (Pizza.count = 1000). That's why I was trying to override "new".

I think this is the trickiest part which makes me ask myself is my approach is in the right direction, or if I simply should stop reling so much on these language mechanisms and instead add myself some logic to let the counting happen only if a object of a Pizza class has entered ObjectSpace..

I am just looking for the more elegant, non bloated way to obtain this using language features..

In either case help would be appreciated..

Thanks again.

Community
  • 1
  • 1
Redoman
  • 3,059
  • 3
  • 34
  • 62
  • 1
    In your case Pizza.new no longer returns an instance of pizza - sounds important to me ! – Frederick Cheung Oct 15 '12 at 07:14
  • so are you saying that by overriding .new I prevent the object from being created correctly ? I verified it and you are right, fav_pizza.class # => Fixnum. It's not a "Pizza" anymore! So can you (or anyone else) tell me what the original new method does, or how to inspect it by reflection, or where to read more on this ? Thanks – Redoman Oct 15 '12 at 18:29
  • ok my other questions got answered in the marked right answer, but thank you very much for pointing what the consequences of overriding "new" could be! Helped me so much in understanding another important part of this language! so +1 :) – Redoman Oct 18 '12 at 01:37

2 Answers2

9

You do not need to override new. Try this:

class Pizza
  @count = 0
  class << self
    attr_accessor :count
  end

  def initialize
    self.class.count += 1
  end
end

Pizza.new
Pizza.new
puts Pizza.count

Please note that Pizza.count will not go down when the pizzas are garbage collected, and it will not go up when they are duped.

To answer your other questions: I'm not sure how you can inspect the new method, but it is probably written in C so you should look in the Ruby source code. I think it basically does this.

def new(*args, &block)
  o = allocate
  o.initialize(*args, &block)
  o
end

You could probably get away with overriding new as long as you called super at some point. (Not saying this is a good idea, but for example:

class Foo
  def self.new
    # do whatever stuff you want here
    super
  end
end

)

David Grayson
  • 84,103
  • 24
  • 152
  • 189
  • I don't think i clearly understood your latest sentence, could please expland it just a little bit? Anyway thanks i am marking your answer right because it totally made me clear the right approach! :) – Redoman Oct 18 '12 at 01:27
  • The last sentence was unimportant since I already gave you an example of how to do things the correct way. If you want to see the incorrect way, please see my latest edit above. – David Grayson Oct 18 '12 at 01:49
  • Wait. Now that I'm thinking about it, I took in consideration your solution earlier, but I did not go with it because by defining a setter and a getter with attr_accessor on "count" class instance variable, "count" would be accessible anytime (Pizza.count = 1000) not only on onject instantiation. As a bigger "security" measure, I wanted Pizza class to be able to count only at object instantiation time and not having a setter method,that's why I was trying to (wrongly) override "new". Now that my purpose is more clear, i'm even more puzzled than before.. how can I implement it this way? Thanks! – Redoman Oct 18 '12 at 02:50
  • I should add "..if the overriding 'new' is bad" ? Do i need to completely change my approach and add in some logic somewhere? (i.e. let the counting happen only when a new verified object of given class comes into play (ObjectSpace?) ) – Redoman Oct 18 '12 at 03:00
  • Just use the first block of code in my answer above and don't worry about "security". – David Grayson Oct 18 '12 at 03:08
  • Why shouldn't I worry about scope availabily of a certain functionality that I _need_ to be restricted to a certain situation ? – Redoman Oct 18 '12 at 03:27
  • In general, any part of your Ruby code can do anything it wants using things like `send` and `instance_variable_get`. OK fine. It's not pretty, but it will probably work: `class Foo; @count = 0; def count; @count; end; def self.new; @count += 1; super; end; end;` – David Grayson Oct 18 '12 at 03:50
  • Yes, thank you, i understood already the syntax for overriding and extending the "new" method with super. And so far it turned out to be the cleanest and most coherent way to do what I am trying to do. If someone else comes with another or better idea please let me know of it! @David Grayson thanks for all your help! You made me also investigate a bit more on code-reflection. It's crazy in the way it allows you to disobey to the code you have just written, but in turn, it is still just valid code :) – Redoman Oct 18 '12 at 13:14
1

you are right. using class variables is mind boggling some times. i always forget if the get inherited or not etc.

your approach is right, but...

NEVER override new implement initialize instead! it will get called during the init process of every object. see the docs here: http://apidock.com/ruby/v1_9_3_125/Class/new

phoet
  • 18,688
  • 4
  • 46
  • 74
  • getting back on this.. what is it so wrong to override new and then call super? What made you type "never" in capitals ? Thanks. – Redoman Oct 02 '14 at 18:58
  • 1
    there is a "principle of least surprise" that you should follow as a convention. not overwriting new is one of those things. it's just a very efficient way of shooting yourself in the foot. – phoet Oct 02 '14 at 23:55
  • 1
    Well thanks I know that principle already since long time, but when understanding things that's not much useful. – Redoman Oct 03 '14 at 00:43