Your updated question looks quite different now. If I understand you correctly, you want to hook into object allocation and initialization, which has absolutely nothing whatsoever to do with metaclasses. (But you still don't write what it is that you actually want to do, so I might still be off.)
In some object-oriented languages, objects are created by constructors. However, Ruby doesn't have constructors. Constructors are just factory methods (with stupid restrictions); there is no reason to have them in a well-designed language, if you can just use a (more powerful) factory method instead.
Object construction in Ruby works like this: object construction is split into two phases, allocation and initialization. Allocation is done by a public class method called allocate
, which is defined as an instance method of class Class
and is generally never overriden. (In fact, I don't think you actually can override it.) It just allocates the memory space for the object and sets up a few pointers, however, the object is not really usable at this point.
That's where the initializer comes in: it is an instance method called initialize
, which sets up the object's internal state and brings it into a consistent, fully defined state which can be used by other objects.
So, in order to fully create a new object, what you need to do is this:
x = X.allocate
x.initialize
[Note: Objective-C programmers may recognize this.]
However, because it is too easy to forget to call initialize
and as a general rule an object should be fully valid after construction, there is a convenience factory method called Class#new
, which does all that work for you and looks something like this:
class Class
def new(*args, &block)
obj = allocate
obj.initialize(*args, &block)
return obj
end
end
[Note: actually, initialize
is private, so reflection has to be used to circumvent the access restrictions like this: obj.send(:initialize, *args, &block)
]
That, by the way, is the reason why to construct an object you call a public class method Foo.new
but you implement a private instance method Foo#initialize
, which seems to trip up a lot of newcomers.
However, none of this is in any way baked into the language. The fact that the primary factory method for any class is usually called new
is just a convention (and sometimes I wish it were different, because it looks similar to constructors in Java, but is completely different). In other languages, the constructor must have a specific name. In Java, it must have the same name as the class, which means that a) there can be only one constructor and b) anonymous classes can't have constructors because they don't have names. In Python, the factory method must be called __new__
, which again means there can be only one. (In both Java and Python, you can of course have different factory methods, but calling them looks different from calling the default, while in Ruby (and Smalltalk from whence this pattern originated) it looks just the same.)
In Ruby, there can be as many factory methods as you like, with any name you like, and a factory method can have many different names. (For collection classes, for example, the factory method is often aliased to []
, which allows you to write List[1, 2, 3]
instead of List.new(1, 2, 3)
which ends looking more like an array, thus emphasizing the collection-ish nature of lists.)
In short:
- the standardized factory method is
Foo.new
, but it can be anything
Foo.new
calls allocate
to allocate memory for an empty object foo
Foo.new
then calls foo.initialize
, i.e. the Foo#initialize
instance method
- all three of those are just methods like any other, which you can undefine, redefine, override, wrap, alias and whatnot
- well, except
allocate
which needs to allocate memory inside the Ruby runtime which you can't really do from Ruby
In Python, __new__
roughly corresponds to both new
and allocate
in Ruby, and __init__
exactly corresponds to initialize
in Ruby. The main difference is that in Ruby, new
calls initialize
whereas in Python, the runtime automatically calls __init__
after __new__
.
For example, here is a class which only allows a maximum of 2 instances created:
class Foo
def self.new(*args, &block)
@instances ||= 0
raise 'Too many instances!' if @instances >= 2
obj = allocate
obj.send(:initialize, *args, &block)
@instances += 1
return obj
end
attr_reader :name
def initialize(name)
@name = name
end
end
one = Foo.new('#1')
two = Foo.new('#2')
puts two.name # => #2
three = Foo.new('#3') # => RuntimeError: Too many instances!