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.
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.
Using
ObjectSpace
to access the objects is the cause of the problem: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.- 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.
- 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.
The fix is to use a container to hold the objects. Simply
push
-ing them on to anArray
creates the necessary references, and stops garbage collection being a problem for you. You should use that variable to find and count your objects, notObjectSpace
.
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 dup
ed.
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
How to List the Available Variables in an Ruby Erb Template
Getting Headers from a Ruby Net::Http Request Before Making the Request
How to Save Heroku Logs to Text File
Errno::Econnreset: Connection Reset by Peer in Rails Using Rest-Client
Subtract Values in Hash from Corresponding Values in Another Hash
Issues Installing Ruby and Rails and Devkit on Windows 7 X64 - Fix Needed
Cucumber Not Finding Step Definitions
Ruby Dot Parenthesis Call Syntax
How to Mark a Cucumber Scenario as Pending
How to Strip Leading and Trailing Quote from String, in Ruby
Rspec: Should Be (This or That)
How to Convert Boolean Values to Integers
Ruby Inside JavaScript Block [Slim Template]
Camelcase Column in Postgresql Database in Rails (Activerecord)