34

Is there any way in Ruby for a class to know how many instances of it exist and can it list them?

Here is a sample class:

class Project

  attr_accessor :name, :tasks

  def initialize(options)
    @name = options[:name]
    @tasks = options[:tasks]
  end

  def self.all
    # return listing of project objects
  end

    def self.count
          # return a count of existing projects
    end


end

Now I create project objects of this class:

options1 = {
  name: 'Building house',
  priority: 2,
  tasks: []
}

options2 = {
  name: 'Getting a loan from the Bank',
  priority: 3,
  tasks: []
}

@project1 = Project.new(options1)
@project2 = Project.new(options2)

What I would like is to have class methods like Project.all and Project.count to return a listing and count of current projects.

How do I do this?

the Tin Man
  • 158,662
  • 42
  • 215
  • 303
Amit Erandole
  • 11,995
  • 23
  • 65
  • 103

4 Answers4

51

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
Andrew Haines
  • 6,574
  • 21
  • 34
  • Do you need to `include ObjectSpace` in the class for this to work? – onebree Oct 20 '15 at 14:24
  • 2
    @HunterStevens no, we're not mixing the module into our class, just calling a method on it – Andrew Haines Oct 20 '15 at 14:34
  • 4
    **WARNING**: this solution can make it easy to shoot oneself in the foot. If you don't keep a reference to your objects (for example if you do `Project.new` without assigning the result to something), they will be garbage collected at some point and `ObjectSpace.each_object` will obviously stop reporting them. Using `@@instances = []` instead like in rohit89's answer solves this problem by keeping a reference to those objects. – vmarquet Jan 29 '17 at 16:28
7

One way to do is to keep track of it as and when you create new instances.

class Project

    @@count = 0
    @@instances = []

    def initialize(options)
           @@count += 1
           @@instances << self
    end

    def self.all
        @@instances.inspect
    end

    def self.count
        @@count
    end

end

If you want to use ObjectSpace, then its

def self.count
    ObjectSpace.each_object(self).count
end

def self.all
    ObjectSpace.each_object(self).to_a
end
Andrew Haines
  • 6,574
  • 21
  • 34
rohit89
  • 5,745
  • 2
  • 25
  • 42
  • This is what I'd do. It'll work for sure in all Ruby implementations, and can be extended for different purposes if needed. – Teemu Leisti Oct 21 '14 at 13:28
4
class Project
    def self.all; ObjectSpace.each_object(self).to_a end
    def self.count; all.length end
end
sawa
  • 165,429
  • 45
  • 277
  • 381
3

Maybe this will work:

class Project
  class << self; attr_accessor :instances; end

  attr_accessor :name, :tasks

  def initialize(options)
    @name = options[:name]
    @tasks = options[:tasks]

    self.class.instances ||= Array.new
    self.class.instances << self
  end

  def self.all
    # return listing of project objects
    instances ? instances.dup : []
  end

  def self.count
    # return a count of existing projects
    instances ? instances.count : 0 
  end

  def destroy
    self.class.instances.delete(self)
  end
end

But you will have to manually destroy these objects. Maybe other solution can be build based on ObjectSpace module.

yattering
  • 436
  • 4
  • 7
  • I like this but can there should be some built in reflection - Doesn't that exist in ruby? I have no clue how to use the ObjectSpace module. Example would really help – Amit Erandole Jan 14 '13 at 12:34
  • 1
    Well. ObjectSpace lets you interact with garbage collector. This is stuff that I try not to do in my code. You can experiment with `ObjectSpace.each_object(Project).to_a` but I can't help you any more with this. – yattering Jan 14 '13 at 12:52
  • any particular reason why this should be avoided? – Amit Erandole Jan 14 '13 at 12:52
  • For example in [doc](http://www.ruby-doc.org/core-1.9.3/ObjectSpace.html) is written that method `count_objects` is "not expected to work except C Ruby". So you have to be careful. – yattering Jan 14 '13 at 13:01
  • `count_objects` is only expected to work for MRI (aka C Ruby). `each_object` works in MRI, Rubinius and JRuby (tested on the 1.9.3 variants). – Andrew Haines Jan 14 '13 at 13:38