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.
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?
- It calls the
allocate
first to allocate required space in the memory and assign the result toobj
variable. - Invokes
initialize
method on theobj
passing to them all the arguments. - 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 🙂