29

For example:

class Animal

 def make_noise
    print NOISE
 end

end

class Dog < Animal
    NOISE = "bark"
end

d = Dog.new
d.make_noise # I want this to print "bark" 

How do I accomplish the above? Currently it says

uninitialized constant Animal::NOISE
Phrogz
  • 296,393
  • 112
  • 651
  • 745
Tim
  • 6,079
  • 8
  • 35
  • 41

5 Answers5

31

I think that you don't really want a constant; I think that you want an instance variable on the class:

class Animal
  @noise = "whaargarble"
  class << self
    attr_accessor :noise
  end
  def make_noise
    puts self.class.noise
  end
end

class Dog < Animal
  @noise = "bark"
end

a = Animal.new
d = Dog.new
a.make_noise  #=> "whaargarble"
d.make_noise  #=> "bark"
Dog.noise = "WOOF"
d.make_noise  #=> "WOOF"
a.make_noise  #=> "whaargarble"

However, if you are sure that you want a constant:

class Animal
  def make_noise
    puts self.class::NOISE
    # or self.class.const_get(:NOISE)
  end
end
Phrogz
  • 296,393
  • 112
  • 651
  • 745
  • But if I use an instance variable, that means every instance of the Dog class will have to store the noise data, right? The noise will not change between instances of Dogs (ie, Dogs always bark), so that's why I thought of a subclass constant. What do you think? – Tim Jan 26 '12 at 06:58
  • @Tim No, each instance of the Dog class does not store that value. `p Dog.new.instance_eval{ @noise } #=> nil` The single instance of the `Class` that is named `Dog` stores that value. Just as instances of classes are objects that can have instance variables, so classes themselves are objects (instances of the Class class) that can have their own instance variables. – Phrogz Jan 26 '12 at 07:01
  • I've updated the example further to show that these are properties of `Dog` itself, not properties of Dog instances. You could even change `attr_accessor` to `attr_reader` if you wanted to more strongly enforce its constant-ness. – Phrogz Jan 26 '12 at 07:03
  • 4
    You don't need to go through const_get. You can just do `self.class::NOISE`. – Chuck Jan 26 '12 at 07:25
  • @Chuck Nice; given the constant scoping rules that have changed from 1.8 through 1.9.1 through 1.9.2 I wasn't sure of the current state of affairs. – Phrogz Jan 26 '12 at 07:27
  • AFAIK, the scoping rule changes only affect how unqualified references (e.g. "puts FOO") are resolved. `self.class::FOO` gets at the constant right from the class with the `::` operator, so it should work the same way in any version of Ruby. – Chuck Jan 26 '12 at 07:51
  • 1
    tiny refactor: you can use cattr_accessor :noise instead of class << self; attr_accessor :noise; end. – Les Nightingill Dec 06 '15 at 22:09
  • @Phrogz even if you don't use the `class << self` the output will be the same. Can you explain why did you use it? – mhaseeb Feb 15 '16 at 12:30
  • @mhaseeb If you try it yourself, you will see that the code does not work if you remove `class << self`. Without that, `attr_accessor` creates _instance_ methods on the class, so you would be able to change the noise made by each dog you created, but not for the `Dog` class as a whole. Using `class << self` (or `cattr_accessor`, as mentioned in the comments above) causes the `noise` and `noise=` methods to be created on the class object itself. – Phrogz Feb 15 '16 at 17:57
8

one way to do it without class instance variables:

class Animal

 def make_noise
   print self.class::NOISE
 end

end

class Dog < Animal
  NOISE = "bark"
end

d = Dog.new
d.make_noise # prints bark
Patrick Klingemann
  • 8,884
  • 4
  • 44
  • 51
  • This is the best answer for what the OP asks. Other answers give good (better?) strategies to achieve a behavior, but this is how a superclass can access a subclass's constante. – David Hempy Jul 28 '23 at 04:40
0

If you're doing this to configure your sub classes so that the base class has access to the constants then you can create a DSL for them like this:

module KlassConfig
  def attr_config(attribute)
    define_singleton_method(attribute) do |*args|
      method_name = "config_#{attribute}"
      define_singleton_method method_name do
        args.first
      end
      define_method method_name do
        args.first
      end
    end
  end
end

class Animal
  extend KlassConfig
  attr_config :noise

  def make_noise
    puts config_noise
  end
end

class Dog < Animal
  noise 'bark'
end

This way is a bit more performant as on each method call you don't have to introspect the class to reach back (or is it forward?) for the constant.

kreek
  • 8,774
  • 8
  • 44
  • 69
0

I think you have the wrong concept here. Classes in Ruby are similar to classes in Java, Smalltalk, C#, ... and all are templates for their instances. So the class defines the structure and the behavior if its instances, and the parts of the structure and behavior of the instances of its subclasses but not vice versae.

So direct access from a superclass to a constant in a subclass is not possible at all, and that is a good thing. See below how to fix it. For your classes defined, the following things are true:

  • class Animal defines the method make_noise.
  • instances of class Animal may call the method make_noise.
  • class Dogdefines the constant NOISE with its value.
  • instances of Dog and the class Dog itself may use the constant NOISE.

What is not possible:

  • Instances of Animal or the class Animal itself have access to constants of the class Dog.

You may fix that by the following change:

class Animal
  def make_noise
    print Dog::NOISE
  end
end

But this is bad style, because now, your superclass (which is an abstraction about Dog and other animals) knows now something that belongs to Dog.

A better solution would be:

  1. Define an abstract method in class Animal which defines that make_noise should be defined. See the answer https://stackoverflow.com/a/6792499/41540.
  2. Define in your concrete classes the method again, but now with the reference to the constant.
Community
  • 1
  • 1
mliebelt
  • 15,345
  • 7
  • 55
  • 92
  • Actually, it is possible; see my answer. – Phrogz Jan 26 '12 at 07:04
  • I have seen your answer (afterwards), and it is different to mine. It is a nice trick, but bad style to access constants of subclasses in superclasses ... – mliebelt Jan 26 '12 at 07:06
  • I agree that it's sort of a bad style, and I'm not criticizing your answer in general. Just the bit where you say _"So what you want to reach is not possible at all."_ – Phrogz Jan 26 '12 at 07:11
-1

If you want it the Object-Oriented Way (TM), then I guess you want:

class Animal
  # abstract animals cannot make a noise
end

class Dog < Animal
  def make_noise
    print "bark"
  end
end

class Cat < Animal
  def make_noise
    print "meow"
  end
end

d = Dog.new
d.make_noise # prints bark

c = Cat.new
c.make_noise # prints meow

If you want to refactor to prevent duplicating the code for print:

class Animal
  def make_noise
    print noise
  end
end

class Dog < Animal
  def noise
    "bark"
  end
end

class Cat < Animal
  def noise
    if friendly
      "meow"
    else
      "hiss"
    end
  end
end

d = Dog.new
d.make_noise # prints bark

c = Cat.new
c.make_noise # prints meow or hiss
Christopher Oezbek
  • 23,994
  • 6
  • 61
  • 85