Dear Ruby (1): What about Arguments when Inheriting?

With most of my code becoming more and more stateless and functional, I haven’t given up leveraging Ruby’s excellent way of defining declarative APIs and DSL. Ruby is just too good at that.

However, I am missing a crucial tool in Ruby: being able to pass dynamic arguments to the inheritance, exactly the way I can pass arguments to the object constructor. In short, here’s what I would love to have.

class Memo::Render < Render, engine: Render::JSON 
  property :title
  property :body
end

The Render class could then use those additional arguments in its inherited hook, acting as an initialize on the class level.

class Render
  def self.inherited(subclass, options)
    super
    subclass.engine = options[:engine]
  end

  # def self.property(name); end
  # def self.engine=(name);  end
end

This would also work for anonymous classes, of course.

Memo::Render = 
  Class.new( Render, engine: Render::JSON) 

Looks very Ruby-esque, doesn’t it?

The Problem with inherited

Currently, you need to set class variables in the inherited class to configure it.

class Memo::Render < Render
  def self.engine
    Render::JSON 
  end
end

The massive drawback here (a design flaw in Ruby?) is that this override is evaluated too late, after the inherited hook is called. Check out the exemplary inherited method in the superclass Render and how it behaves.

class Render
  def self.engine; end

  def self.inherited(subclass)
    puts subclass        #=> Memo::Render
    puts subclass.engine #=> nil !!!
  end
end

As you can see, even though Memo::Render implements engine, it isn’t evaluated at inherited time, making this hook more or less useless if you want to cleanly initialize and setup a subclass at compile-time.

One solution could be the proposed extension of the hook, however, the complexity of this change is completely unknown to me due to my lack of Ruby core participation.

11 thoughts on “Dear Ruby (1): What about Arguments when Inheriting?

    1. Cool idea, but I guess this will be the same problem: The class body is evaluated _after_ its inherited hook was called. Have you tested it?

      Like

  1. > class Memo::Render < Render, engine: Render::JSON

    The engine is a customization of the the subclass. Why pass it the the superclass so that it can pass back to the subclass

    Like

  2. Please ignore the previous accidental submission.

    > class Memo::Render < Render, engine: Render::JSON

    The engine is a customization of the the subclass. Why pass the engine to the superclass so that it can pass it back to the subclass, when you can set it on the subclass directly:

    class Memo::Render < Render
    @engine = Render::JSON
    end

    This can also be done with class factory method

    Memo::Render = Render.extend engine: Render::JSON do
    # Memo::Render body
    end

    def Render.extend(engine:, &class_def)
    sub = Class.new(Render, &class_def)
    sub.engine = engine # or whatever
    sub
    end

    Like

Leave a comment