59

In Ruby, is there a way to 'override' a constant in a subclass in such a way that calling an inherited method from the subclass results in that method using the new constant instead of the old one? For example:

class SuperClass
  CONST = "Hello, world!"
  def self.say_hello
    CONST
  end
end

class SubClass < SuperClass
  override_const :CONST, "Hello, Bob!"
end

SuperClass.say_hello # => "Hello, world!"
SubClass.say_hello   # => "Hello, Bob!"

If not, is there perhaps a way to do something like this instead?

class SuperClass
  CONST = "Hello, world!"
  def self.say_hello
    CONST
  end
end

SubClass = SuperClass.clone
SubClass.send(:remove_const, :CONST)
SubClass.const_set(:CONST, "Hello, Bob!")

SubClass.say_hello # => "Hello, Bob!"

Note: I tried my second example out in irb, and it seems to work except that class methods can't seem to access CONST after I clone the class:

irb(main):012:0> SubClass.say_hello
NameError: uninitialized constant Class::CONST
        from (irb):4:in `say_hello'
        from (irb):12
        from C:/Ruby193/bin/irb:12:in `<main>'
lulalala
  • 17,572
  • 15
  • 110
  • 169
Ajedi32
  • 45,670
  • 22
  • 127
  • 172

3 Answers3

89

I've done this by simply redefining the constant in the subclass, and then referring to it in methods as self.class::CONST in instance methods and self::CONST in class methods. In your example:

class SuperClass
  CONST = "Hello, world!"
  def self.say_hello
    self::CONST
  end
end

class SubClass < SuperClass
  CONST = "Hello, Bob!"
end

SubClass.say_hello #=> "Hello, Bob!"
Zach Kemp
  • 11,736
  • 1
  • 32
  • 46
  • 5
    That seems like a great way of doing it. Unfortunately it won't work for my situation since the class that I want to override the constants of is part of an external library (a gem) that I don't really want to mess with. – Ajedi32 Nov 05 '12 at 14:53
  • 1
    @Ajedi32 All classes are open for modification, including internal lib or external gem. You can just use `class String /*blah blah*/ end` to modify string class, etc. – SwiftMango Nov 05 '12 at 15:55
  • 1
    @texasbruce Yes, but in this case I don't want to modify the gem; I just want a clone of a class inside the gem that has a slightly modified constant. Overriding methods or constants in the class itself would mess up the internal workings of the gem, which isn't what I want to do. – Ajedi32 Nov 05 '12 at 16:04
  • 1
    We want to override the constants, so that their direct references to the constants inside the gem should use the overridden constant definition. I haven't figured out a way to do that. – Hari Aug 14 '13 at 14:39
  • "I don't want to modify the behavior of the gem, I just want to modify the way the gem behaves." – Jazz May 21 '15 at 20:58
  • 3
    @Jazz Not quite. I want the gem to behave exactly the way it is now. It's the subclass whose behavior I want to change, not the superclass, and I want to do it without having to duplicate a massive chunk of the gem's code in my codebase. (For example, if the gem references `CONST` in 20 of its methods, I don't want to override all 20 of those methods, duplicating all their code, just so I can change them to refer to `self::CONST` instead.) – Ajedi32 Aug 18 '15 at 20:22
  • @Ajedi32 Consider submitting a pull request / patch to the gem's project, perhaps? – Pistos Dec 16 '15 at 22:42
  • @Pistos Won't work in my case, unfortunately, since the behavior change I need would break the gem. I need the change for a very specific usage of one of the gem's internal classes in my code, not for the behavior of the library itself. Ultimately what I ended up doing was just copy-pasting the code into a new class in my project and making the change there. Maybe not ideal, but it works. – Ajedi32 Dec 17 '15 at 01:10
  • @Ajedi32 Can't you just create a new class, which inheris the class you wanna override its constant instead of copying the whole code into a different class ? – Gilg Him Jan 08 '20 at 16:26
  • @GilgHim No that doesn't work. Inherited methods continue to use the value of the old constant. See the question. – Ajedi32 Jan 08 '20 at 18:05
15

You can refer to a constant like this in the parent class:

For a instance method: self.class::CONST

For a class method: self::CONST

class SuperClass
  CONST = "Hello, world!"
  def print_const
    puts self.class::CONST
  end

  def self.print_const
    puts self::CONST
  end

end

class SubClass < SuperClass
  CONST = "Hello, Bob!"
end

SubClass.new.print_const #=> "Hello, Bob!"
SubClass.print_const #=> "Hello, Bob!"
Bhacaz
  • 426
  • 5
  • 7
2

If you have the luxury of being able to change the base class, consider wrapping the "constants" that need changing in class methods in the base class and overriding them as needed in subclasses. This removes the potential for confusion between parent and subclass constants. For the example, this would be as follows:

class SuperClass
  CONST = "Hello, world!".freeze

  def self.const
    CONST
  end

  def self.say_hello
    const
  end
end

class SubClass < SuperClass
  CONST = "Hello, Bob!".freeze

  def self.const
    CONST
  end
end

SubClass.say_hello #=> "Hello, Bob!
coberlin
  • 508
  • 5
  • 7