1
votes

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...

3

3 Answers

1
votes

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}
1
votes

If you want one that's generic enough to adapt to changes made in the base class after a subclass has been derived from it:

class Base
  def self.option(name,val)
    @options ||= { }

    @options[name] = val
  end

  def self.options
    @options ||= { }

    if (self == Base)
      @options
    else
      @options.merge(self.superclass.options)
    end
  end
end

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

class SubFoo < Foo
  option :three, 3
end

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

class Foo
  option :four, 4
end

Foo.options
#=> {:one=>1, :two=>2, :four=>4}
SubFoo.options
#=> {:three=>3, :one=>1, :two=>2, :four=>4}
0
votes
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

class Bar < Foo
  @options = superclass.options.clone
  option :three, 3
end

class Baz < Foo
  @options = superclass.options.clone
  option :four, 4
end

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

This is the only thing I can think of that will work, you simply clone @options from the superclass. This way, each class will have their own individual instance variable.