Weird Ruby class initialization logic?

Some open source codes that I integrate in my application have some classes that include code for this:

class SomeClass < SomeParentClass def self.new(options = {}) super().tap { |o| # do something with `o` according to `options` } end def initialize(options = {}) # initialize some data according to `options` end end 

As far as I understand, both self.new and initialize do the same thing - the last "during construction" and the first "after construction", and this looks like a terrible pattern to use - why divide the initialization of the object into two parts, where, obviously, "The Wrong Think (tm)"?

+8
constructor ruby
source share
3 answers

Ideally, I would like to see what is inside the super().tap { |o| block super().tap { |o| , because although this seems like bad practice, there may be some kind of interaction required before or after the initialize call.

Without context, it is possible that you are just looking at something that works, but is not considered good practice in Ruby.

However, it is possible that the approach of the individual self.new and initialize methods allows the framework designer to implement the part suitable for the subclass, and still ensure that the configuration required by the structure is complete without a bit of inconvenient documentation that requires the use of super() . It would be a little easier to document a cleaner API if the end user gets the functionality they expect only by subclassing class MyClass < FrameworkClass and without an extra note, for example:

When you implement the initialize subclass, remember to put super at the beginning, otherwise the magic will not work

., personally, I think that the design is doubtful, but I think that at least there will be a clear motivation.

Perhaps there are deeper reasons for the Ruby language to run code in the self.new custom block β€” for example, it might allow the designer to switch or modify a specific object (even returning an object of another class) before returning it. However, I rarely saw such things in practice, there is almost always some other way to achieve the goals of such code without setting new .


Examples of custom / different Class.new methods raised in the comments:

  • Struct.new , which may optionally take the class name and return the objects of this dynamically created class.

  • Table inheritance for ActiveRecord , which allows the end user to load an object of an unknown class from the table and get the right object.

The latter could have been avoided by using another ORM project for inheritance (although all such schemes have pros and cons).

The first (Structs) is the main language, so now it should work like this (although designers could choose a different method name).

+4
source share

It is impossible to say why this code exists without seeing the rest of the code.

However, in your question I want to say something:

As far as I understand, both self.new and initialize do the same thing - the last "during construction" and the first "after construction"

They do not do the same.

Ruby constructs an object in two steps: Class#allocate selects a new empty object from the object space and sets its inner class to a pointer to self . Then you initialize an empty object with some default values. Usually this initialization is done by the initialize method, but this is just a convention; the method can be called any that you like.

There is an additional helper method Class#new , which does nothing but perform two steps in the sequence, for the convenience of the programmer:

 class Class def new(*args, &block) obj = allocate obj.send(:initialize, *args, &block) obj end def allocate obj = __MagicVM__.__allocate_an_empty_object_from_the_object_space__ obj.__set_internal_class_pointer__(self) obj end end class BasicObject private def initialize(*) end end 
+3
source share

Constructor new must be a class method, since you start from where there is no instance; you cannot call this method for a specific instance. On the other hand, the initialize initialize procedure is better defined as an instance method because you want to do something with a specific instance. Therefore, Ruby is intended to internally invoke the initialize instance method in a new instance immediately after it is created by the class method new .

+2
source share

All Articles