How to Count Existing Instances of a Class in Ruby

How to count instances of class without counting reassignment?

In the real world you wouldn't be counting instances in memory, you'd be asking a database how many exist. You need to think in terms of a database.

Your use of a to repeatedly contain the Ticket instance is wrong. You should be using an Array, Hash or Set to maintain the list, then ask the container how many exist:

require 'set'

class Ticket
attr_accessor :price
attr_reader :event, :venue

@@tickets = Set.new

def initialize(event, venue)
@event = event
@venue = venue

@@tickets << self
end

def delete
@@tickets.delete(self)
end

def self.count
@@tickets.size
end

end

a = Ticket.new("Michael Buble", "Staples")
b = Ticket.new("Frank Sinatra", "Madison Square Garden")

puts "Ticket count of #{Ticket::count}"
b.delete
puts "Ticket count of #{Ticket::count}"

You can build this out by adding ways to retrieve a particular instance from @@tickets, add a to_s so you can list them, but, in the end, you'll want to use a real database. If your code were to crash for any reason, your entire list of tickets would disappear, which would be unacceptable in real life.

Count initializations of Ruby Class

Instance variables belong to objects (aka instances), that's why they are called instance variables after all. Your first @bar is an instance variable of Foo, your second @bar is an instance variable of the newly-created instance of Foo. Those are two completely different objects (they aren't even of the same class: the newly-created instance is of class Foo, whereas Foo is of class Class).

You obviously need to increment @bar in a method called on Foo, not in a method called on instances of Foo. So, can we think about a method that is a) called on Foo and b) called everytime an instance is created? What about new?

class Foo
@bar = 0

def self.new(*)
@bar += 1
super
end
end

Okay, technically speaking, this doesn't count the number of instances, only the number of times new was called. Sometimes, instances get created without calling new, e.g. when de-serializing. This should be the closest you can get without resorting to ugly hacks of the interpreter internals.

You might think you can override allocate instead (I thought so, too), but I just tested it and it doesn't work. Presumably, the default implementation of new doesn't call allocate via normal means but actually uses the interpreter internal implementation directly.

How to count existing instances of a class in ruby?

It can work, but there's circular reference in finalization. Your finalizer depends on the binding of an object that should be collected. See this solution.

class Foo
@@no_foo = 0

def initialize
@@no_foo += 1
ObjectSpace.define_finalizer(self, Foo.method(:delete))
end

def self.delete id # also this argument seems to be necessary
@@no_foo -= 1
end

def self.no_foo
@@no_foo
end
end

Foo.no_foo # => 0
1000.times{Foo.new}
Foo.no_foo # => 1000

GC.start
Foo.no_foo # => 0

Is the `@count` an instance variable or class variable in Ruby?

@count is always an instance variable, but it can be an instance variable on a class if it is declared in that context.

In this case, @count is a class instance variable. In other words, by writing @count inside of a class level method you are assigning a variable to that class.

By writing @count inside of a instance method, you are assigning an instance variable that is available in that particular instance only.

If you declare a variable with @@count you get a class variable.

THe main difference between class variables and class instance variables is that class variables are retained in inheritance.

class Foo
@@klass = "class level variable"
@klass_instance = "class instance level variable"
end

class Bar < Foo
end

puts Foo.instance_variables.inspect # => [:@klass_instance]
puts Foo.class_variables.inspect # => [:@@klass]

puts Foo.instance_variable_get(:@klass_instance)
# => "klass instance level variable"
puts Foo.class_variable_get(:@@klass)
# => "class level variable"

# The class variable is inherited, but the class instance variable is not

puts Bar.instance_variables.inspect # => []
puts Bar.class_variables.inspect # => [:@@klass]

# The @@klass variable is shared between all classes in the downward inheritance chain
# So for example:
Foo.class_variable_set(:@@klass, "foo")
puts Bar.class_variable_get(:@@klass) # => "foo"

How to count the number of objects created in Ruby

You should use

ObjectSpace.count_objects

For example, this is what it outputs on a fresh IRB session:

{
:TOTAL => 30161,
:FREE => 378,
:T_OBJECT => 152,
:T_CLASS => 884,
:T_MODULE => 30,
:T_FLOAT => 4,
:T_STRING => 11517,
:T_REGEXP => 165,
:T_ARRAY => 3395,
:T_HASH => 180,
:T_STRUCT => 2,
:T_BIGNUM => 2,
:T_FILE => 15,
:T_DATA => 1680,
:T_MATCH => 99,
:T_COMPLEX => 1,
:T_NODE => 11620,
:T_ICLASS => 37
}

Is there a limit to the number of instances a class can have in Ruby?

This answer based on comments by myself and Max that seem to of resolved the problem.

  1. The code in the question is not being affected by any limit on number of objects. There is no limit inherent in the Ruby language, and most implementations allow for large numbers of objects, more than will fit into terabytes of memory. A script will typically run out memory or CPU time before it runs out of object pointers.

  2. Using ObjectSpace to access the objects is the cause of the problem:

    1. ObjectSpace is a useful debugging tool or meta-programming tool, you can find current objects using it, but it does not maintain any active references.
    2. Ruby will clear up un-referenced objects, this is called garbage collection. It does this by checking all active bindings and following their object references (and all their child references etc). Anything not marked as "in use" in that mark phase can be removed in a later sweep phase.
    3. The garbage collection runs concurrently with your code, and only starts at all once the script has consumed at least some memory. This explains the puzzling changes of number when you tried different tests.
  3. The fix is to use a container to hold the objects. Simply push-ing them on to an Array creates the necessary references, and stops garbage collection being a problem for you. You should use that variable to find and count your objects, not ObjectSpace.

Can a class in ruby store the number of instantiated objects using a @class_instance_variable and not a @@class_variable?

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

)

How do I list all objects created from a class in Ruby?

You can use the ObjectSpace module to do this, specifically the each_object method.

ObjectSpace.each_object(Project).count

For completeness, here's how you would use that in your class (hat tip to sawa)

class Project
# ...

def self.all
ObjectSpace.each_object(self).to_a
end

def self.count
all.count
end
end

How to find each instance of a class in Ruby

The solution is to use ObjectSpace.each_object method like

ObjectSpace.each_object(Pokemon) {|x| p x}

which produces

<Pokemon:0x0000010098aa70>
<Pokemon:0x00000100992158>
=> 2

Details are discussed in the PickAxe book Chapter 25



Related Topics



Leave a reply



Submit