Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Weird Ruby class initialization logic?

Some open source code I'm integrating in my application has some classes that include code to that effect:

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 latter one "during construction" and the former one "after construction", and it looks to me like a horrible pattern to use - why split up the object initialization into two parts where one is obviously "The Wrong Think(tm)"?

like image 793
Guss Avatar asked Apr 25 '17 09:04

Guss


People also ask

Does a Ruby class need an initialize method?

The initialize method is useful when we want to initialize some class variables at the time of object creation. The initialize method is part of the object-creation process in Ruby and it allows us to set the initial values for an object.

What does .NEW do in Ruby?

The new function in Ruby is used to create a new Enumerator object, which can be used as an Enumerable. Here, Enumerator is an object. Parameters: This function does not accept any parameters.

What is initialization method?

The main purpose of the initialization method is to set up the initial hierarchy of the RODM data cache. Some functions can be used only by the initialization method. The RODM load function can be used as the RODM initialization method. Parent topic: Object-Independent Methods.


1 Answers

Ideally, I'd like to see what is inside the super().tap { |o| block, because although this looks like bad practice, just maybe there is some interaction required before or after initialize is called.

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

However, maybe the approach of separate self.new and initialize methods allows the framework designer to implement a subclass-able part of the framework and still ensure setup required for the framework is completed without slightly awkward documentation that requires a specific use of super(). It would be a slightly easier to document and cleaner-looking API if the end user gets functionality they expect with just the subclass class MyClass < FrameworkClass and without some additional note like:

When you implement the subclass initialize, remember to put super at the start, otherwise the magic won't work

. . . personally I'd find that design questionable, but I think there would at least be a clear motivation.

There might be deeper Ruby language reasons to have code run in a custom self.new block - for instance it may allow constructor to switch or alter the specific object (even returning an object of a different class) before returning it. However, I have very rarely seen such things done in practice, there is nearly always some other way of achieving the goals of such code without customising new.


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

  • Struct.new which can optionally take a class name and return objects of that dynamically created class.

  • In-table inheritance for ActiveRecord, which allows end user to load an object of unknown class from a table and receive the right object.

The latter one could possibly be avoided with a different ORM design for inheritance (although all such schemes have pros/cons).

The first one (Structs) is core to the language, so has to work like that now (although the designers could have chosen a different method name).

like image 83
Neil Slater Avatar answered Nov 08 '22 21:11

Neil Slater