Andrew Andrew - 3 months ago 11
Ruby Question

Ruby inheritable class macro

Let's say I want to make a macro in Ruby.

class Base
def self.option(name,val)
options[name] = val
end

def self.options
@options ||= {}
end
end

class Foo < Base
option :one, 1
option :two, 2
end

Foo.options #=> {:one => 1, :two => 2}


OK, easy enough.

But what if I want to inherit Foo?

class Bar < Foo
end

Bar.options #=> {}


That sucks.

So it's clear the issue is the class instance variable is unique per class, ie.
@options
inside
Bar
is not the same as
@options
inside
Foo
.

So maybe a class variable? I've never been able to figure out a valid use for one of those, let's try it.

# the rest of the code unchanged
class Base
def self.options
@@options ||= {}
end
end

Bar.options #=> {:one => 1, :two => 2}


Hey that worked! ... didn't it?

class Baz < Foo
option :three, 3
end

Foo.options #=> {:one => 1, :two => 2, :three => 3}
Bar.options #=> {:one => 1, :two => 2, :three => 3}
Baz.options #=> {:one => 1, :two => 2, :three => 3}


X-|

Ok so, I've been googling around about this and I'm not seeing anything helpful. I tried a few variations on trying to read the superclass options (if defined) but got nowhere. I figured I may as well ask.

Any of you know how to do this? Or is it just not possible...

Answer

I believe this is what you need:

class Base
  def self.option(name, val)
    options[name] = val
  end

  def self.options
    @options ||= if self.superclass.respond_to?(:options)
                   self.superclass.options.dup
                 else
                   {}
                 end
  end
end

class Foo < Base
  option :one, 1
  option :two, 2
end

class Bar < Foo
  option :three, 3
end

class Hello < Bar
  option :world, 4
end

puts Foo.options # {:one=>1, :two=>2}
puts Bar.options # {:one=>1, :two=>2, :three=>3}
puts Hello.options #{:one=>1, :two=>2, :three=>3, :world=>4}