Ruby `allocate` method

TIL that Ruby, or to be more precise Class class, offers allocate public instance method. According to the documentation the method:

Allocates space for a new object of class’s class and does not call initialize on the new instance. The returned object must be an instance of class.

The same documentation includes a nice code snippet explaining the above description:

klass = Class.new do
  def initialize(*args)
    @initialized = true
  end

  def initialized?
    @initialized || false
  end
end

klass.allocate.initialized? #=> false
klass.new.initialized? #=> true

To learn even more about it, you can check its tests in the official Ruby repository.

Thanks to digging into Ruby source code (tests file of the Class.new method to be precise) I learned even more.

klass = Class.new do
  def self.allocate
    raise "allocate should not be called"
  end
end

2.5.3 :047 > klass.new
 => #<#<Class:0x00007fbd5a05c340>:0x00007fbd5a03f678>

If I overwrote allocate method in any of my classes, new one would still call the original allocate defined in Class class. What a surprise 🙂

What are allocate responsibilities?

Classes in Ruby are first-class objects, each is an instance of Class class. To create a new object of a concrete class we usually call new method which is also defined within Class class. What does the new do?

  1. It calls the allocate first to allocate required space in the memory and assign the result to obj variable.
  2. Invokes initialize method on the obj passing to them all the arguments.
  3. Returns obj object.

We can translate the above points into some piece of code:

class Class
  def new(*args)
    obj = allocate
    obj.initalize(*args)
    obj
  end
end

What value does allocate provide for us?

Simply speaking, it gives a freedom to define custom object initializers. Speaking more technically, it gives us more control over object creation. Let’s consider a simple example:

class User
  def initialize
    @details = {}
  end
end

A common way to make it possible to set @details hash is extending the intialize method itself:

class User
  def initialize(details = {})
    @details = details
  end

The other possible solution is to use allocate method inside a custom class public method:

class User
  def self.initialize_with_age(age)
    obj = allocate
    obj.instance_variable_set(:@details, { age: age })
    obj
  end
end

An example usage:

2.5.3 :020 > user = User.new
 => #<User:0x00007fbd5a02fd90 @details={}>
2.5.3 :022 > user_with_age = User.initialize_with_age(15)
 => #<User:0x00007fbd590f3660 @details={:age=>18}>

Doesn’t allocate add unnecessary magic to Ruby?

In this particular case, we cannot talk about magic. The method is a part of the language invoked every time you call Class.new inside any of your classes. You may find some edge cases where using it may be beneficial. The other option is to remember that it exists, and do not use it directly 🙂

 

Igor Springer

I build web apps. From time to time I put my thoughts on paper. I hope that some of them will be valuable for you. To teach is to learn twice.