8

Let's say I have a class with a few "static" variables. I want to a subclass of that class to be able to override those variables without affecting the original class. This isn't possible using class variables, since those appears to be shared between subclasses and superclasses:

class Foo
  @@test = "a"

  def speak; puts @@test; end
end

class Bar < Foo
  @@test = "b"
end

Bar.new.speak
# b

Foo.new.speak
# b

It isn't possible using constants either:

class Foo
  TEST = "a"

  def speak; puts TEST; end
end

class Bar < Foo
  TEST = "b"
end

Bar.new.speak
# a

Foo.new.speak
# a

Methods defined in the superclass ignores constants in the subclass.

The obvious workaround is to define methods for the variables that needs to be "overridable":

class Foo
  def test; "a"; end
end

But that feels like a hack. I feel like this should be possible using class variables and that I'm probably just doing it wrong. For example, when I subclass Object (which is what happens by default):

class Foo < Object
  @@bar = 123
end

Object.class_variable_get(:@@bar)
# NameError: uninitialized class variable @@bar in Object

Why isn't @@bar set on Object like it was in my Bar < Foo example above?


To summarize: how do I override a variable in a subclass without affecting the superclass?

Hubro
  • 56,214
  • 69
  • 228
  • 381

3 Answers3

8

Class constants does what you want, you just need to use them differently:

class Foo
  TEST = "a"

  def speak
    puts self.class::TEST
  end
end

class Bar < Foo
  TEST = "b"
end

Bar.new.speak # => a
Foo.new.speak # => b
henrebotha
  • 1,244
  • 2
  • 14
  • 36
Sahil Dhankhar
  • 3,596
  • 2
  • 31
  • 44
  • Edited the text of the answer to make more sense, hope you don't mind. This seems like the most correct way to me, so I'm accepting the answer. – Hubro Oct 15 '13 at 13:54
  • @Codemonkey Your original question was about variables, not constants. Although Ruby lets you overwrite constants, you'll get warnings for doing so and it will be confusing for anyone reading your code. Denis's answer is the proper way of implementing per-class variables. – Max Oct 15 '13 at 15:49
  • 1
    @Max: If you read the whole question it will be clear that all I'm after is a way to override a *value* tied to a class in a subclass - it's not important whether it's variables or constants. I agree that "variable" may have been a poor choice of words. Using constants like this seems to me to be more straight-forward and intuitive than defining meta-class attribute readers. I haven't gotten any warnings from defining constants in subclasses with the same name as constants in their superclasses (like in this answer's example) – Hubro Oct 15 '13 at 16:59
  • You are correct about not getting warnings when defining a new constant in the subclass. Also it is not necessary to redefine `speak` in the subclass if you define it as it is in this answer. – Max Oct 15 '13 at 17:25
  • @Max thanks! . yes it is not necessary to redefine `speak` . Modified the answer. Even more less code :) – Sahil Dhankhar Oct 16 '13 at 02:21
4

Add a variable to the class itself (as in the class instance, rather than a class variable):

class Foo
  @var = 'A'

  class << self
    attr_reader :var
  end

  def var
    self.class.var
  end
end

class Bar < Foo
  @var = 'B'
end

Foo.var # A
Foo.new.var # A
Bar.var # B
Bar.new.var # B
Sven R.
  • 1,049
  • 17
  • 24
Denis de Bernardy
  • 75,850
  • 13
  • 131
  • 154
3

The correct way (IMHO) is to use methods, because this way you're using inheritance and virtual dispatching, just as you want to.

Class variables are shared down the hierarchy, not up. That's why @@bar is not available in Object.

Sergio Tulentsev
  • 226,338
  • 43
  • 373
  • 367