simple module for "count my instances" behaviour

J

Julien Thewys

I want to make a simple module that makes its including classes
self-countable.
Here is the code (that does not work, incidentally):

#---code
module Countable
@@counter = 0

def initialize
@@counter += 1
end

module ClassMethods
def population
return @@counter
end
end

def self.included(base)
base.extend(ClassMethods)
end
end

class User
include Countable
end

User.new
User.new
puts User.population
#---code

The error message says that the @@counter class variable is not
initialized in Countable::ClassMethods (so in the population method).
Obviously, this @@counter variable is not the same as the one that is
declared when including Countable, since User.new works as expected.

Can someone explain me why and how to solve this?
 
J

James Coglan

[Note: parts of this message were removed to make it a legal post.]
The error message says that the @@counter class variable is not
initialized in Countable::ClassMethods (so in the population method).
Obviously, this @@counter variable is not the same as the one that is
declared when including Countable, since User.new works as expected.

Can someone explain me why and how to solve this?



I think what you really want is an instance variable in the including class,
since the counter is a property of the class. The instance variable must be
created anew in every including class, whereas I think your code will try to
reuse Countable's @@counter variable for all classes. This code works:

module Countable
def initialize
self.class.instance_eval do
@counter ||= 0
@counter += 1
end
end

module ClassMethods
def population
return @counter || 0
end
end

def self.included(base)
base.extend(ClassMethods)
end
end

class User
include Countable
end

User.new
User.new
puts User.population
 
A

ara.t.howard

I want to make a simple module that makes its including classes
self-countable.
Here is the code (that does not work, incidentally):


something like ?


cfp:~ > cat a.rb
class Class
New = method :new unless defined?(New)
Count = Hash.new{|h,k| h[k] = 0} unless defined?(Count)

def new *a, &b
New.call(*a, &b)
ensure
Count[self] += 1 unless $!
end

def count
Count[self]
end
end

class C; end

3.times{ C.new }
3.times{ Array.new }


p 'C.count' => C.count
p 'Array.count' => Array.count
p 'Hash.count' => Hash.count





cfp:~ > ruby a.rb
{"C.count"=>3}
{"Array.count"=>3}
{"Hash.count"=>0}



a @ http://codeforpeople.com/
 
R

Robert Dober

Maybe there is a way to leave Class alone ;)

This will track instances for all ancestors of InstanceTracker.

module InstanceTracker
# Define a block that defines the included
inclusion = proc { | by_module |
singleton = class << by_module; self end

unless Class === by_module then
singleton.send :define_method, :included, &inclusion
return
end

by_module.instance_variable_set "@__instances__", []
singleton.send :define_method, :inherited, &inclusion
singleton.module_eval do
def new *args, &blk
o = allocate
o.send( :initialize, *args, &blk )
@__instances__ << o
o
end
def instances; @__instances__ end
end
}
# Define included in base module
class << self; self end.
send :define_method, :included, &inclusion
end # module InstanceTracker


M2 = Module::new {
include InstanceTracker
}
class A
include M2
end
B = Class::new A do
def initialize; 42 end
end

A::new
B::new
A::new
p A.instances
p B.instance

----------------------------------

One could take special care of classes that define a custom new, but
that would make the code too complicated for the demonstrational
purpose I feel.

HTH
Robert
 
J

Julien Thewys

James said:
I think what you really want is an instance variable in the including
class,
since the counter is a property of the class. The instance variable must
be
created anew in every including class, whereas I think your code will
try to
reuse Countable's @@counter variable for all classes. This code works:

Thanks James.
I was trying to illustrate the use of class variables with modules but,
as you pointed out, my example is precisely a case where we need class
instance variables
(http://www.martinfowler.com/bliki/ClassInstanceVariable.html).
 
J

Julien Thewys

Robert said:
One could take special care of classes that define a custom new, but
that would make the code too complicated for the demonstrational
purpose I feel.

In fact, the purpose of my example was educational.
Defining a custom new would illustrate how to be evil in my case :)

By the way, I can not think of a legitimate use of redefining new.
 
R

Robert Dober

By the way, I can not think of a legitimate use of redefining new.
Do you mean my code is not legitimate?
I think this is precisely a good use case.

Robert
 
R

Robert Dober

I meant other than for hacking purpose.
Yes but that was my point, I guess if you wanted to expose this method
of tracking, you should check if your classes implement new for some
hacking reason ;). I would prefer the term "metaprogramming" though.
I just wanted to be sure that you are aware of that, if we redefine
new in a legitimate way, someone else might have.

Cheers
Robert
 
A

ara.t.howard

I meant other than for hacking purpose.



there are many good reasons - one is for subclassing, if you override
new clients do not have to remember to call 'super'

class C
def C.new *a, &b
(object = allocate).instance_eval do
init *a, &b
initialize *a, &b
self
end
end

def init
@you = 'do not have to remember to call this'
end
end


class D < C
def initialize
end
end


another reason is for instance in the case of dike.rb, by re-defining
'new' it can track object creation, register a finalizer, and thereby
track the object lifecycle to detect memory leaks

regards.

a @ http://codeforpeople.com/
 
R

Robert Dober

there are many good reasons - one is for subclassing, if you override new
clients do not have to remember to call 'super'

class C
def C.new *a, &b
(object = allocate).instance_eval do
init *a, &b
initialize *a, &b
self
end
end

def init
@you = 'do not have to remember to call this'
end
end


class D < C
def initialize
end
end


another reason is for instance in the case of dike.rb, by re-defining 'new'
it can track object creation, register a finalizer, and thereby track the
object lifecycle to detect memory leaks

regards.

a @ http://codeforpeople.com/
Ara I know your code is quite fine and demonstrating what you wanted
to, I am however still not sure that
I brought my point across :(

The dilemma of a way to redefine new statically (1) or dynamically (2)
is to allow it to tolerate other new implementations in the
inheritance chain.

(1)
class C
def self.new *args, &blk
super(*args, &blk).instance_eval do
...
self
end
end
end

(2)

module M
def self.inherited a_mod
return if Class === a_mod
class << a_mod
alias_method :__old__, :new rescue nil
self
end.module_eval do
def new *args, &blk
__old__(*args,&blk).instance_eval do
...
self
end*

Cheers
Robert
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,769
Messages
2,569,579
Members
45,053
Latest member
BrodieSola

Latest Threads

Top