Ruby class variables

35,283

Solution 1

To contrast @khelll's answer, this uses instance variables on the Class objects:

class Fish
  # an instance variable of this Class object
  @var = 'fish'

  # the "getter"
  def self.v
    @var
  end

  # the "setter"
  def self.v=(a_fish)
    @var = a_fish
  end
end

class Trout < Fish
  self.v = 'trout'
end

class Salmon < Fish
  self.v = 'salmon'
end

p Trout.v   # => "trout"
p Salmon.v  # => "salmon"

Edit: to give instances read-access to the class's instance variable:

class Fish
  def type_of_fish
    self.class.v
  end
end

p Trout.new.type_of_fish   # => "trout"
p Salmon.new.type_of_fish  # => "salmon"

Solution 2

@var mentioned above is called class instance variable, which is different from instance variables... read the answer here to see the diff.

Anyway this is the equivalent Ruby code:

class Fish
  def initialize
    @var = 'fish'
  end

  def v
    @var
  end
end

class Trout < Fish
  def initialize
    @var = 'trout' 
  end
end

class Salmon < Fish
  def initialize
    @var = 'salmon' 
  end
end

puts Trout.new.v
puts Salmon.new.v

Solution 3

Here's the version I ended up figuring out using hobodave's link:

class Fish
  class << self
    attr_accessor :var
  end

  @var = 'fish'
  def v
    self.class.var
  end
end

class Trout < Fish
  @var = 'trout'
end

class Salmon < Fish
  @var = 'salmon'
end

puts (Trout.new).v   # => trout
puts (Salmon.new).v  # => salmon

Notice that subclassing only requires adding an @var -- no need to override initialize.

Solution 4

It's a common mistake made by Java coders coming to Ruby as well, and one of the big conceptual jumps I had to get my head around. At first it seems odd, but it's really one of the cooler aspects of Ruby -- all code is executable, including class definitions.

So, instance variables must be declared inside methods. It has to do with how 'self' is evaluated. 'self' is the current object. The interpreter will lookup method calls and variable references first in 'self':

class Fish
    @var = "foo" # here 'self' == Fish, the constant which contains the class object  
    def foo
        # do foo
    end
end

fish = Fish.new
fish.foo # here 'self' == fish, an instance of Fish

In a class definition, 'self' is set to be the class object being defined, so any references within a class definition will refer to that class object, in this case Fish.

When a method is called on an instance of Fish, however, self is set to be the receiver of the call, the particular instance of Fish. So outside of a method definition, self is the class object. Inside a method, self is the instance of the receiver. This is why @var outside of a method definition is more like a static variable in Java, and @var inside a method definition is an instance variable.

Solution 5

There is a one problem: you can override @var:
Salmon.var = 'shark' will override @var, so
puts (Salmon.new).v # => shark

Share:
35,283

Related videos on Youtube

docwhat
Author by

docwhat

An expert in Jenkins, docker, Kubernetes, and automating workflows for developers. A creative and agile problem solver.

Updated on July 09, 2022

Comments

  • docwhat
    docwhat almost 2 years

    The ruby class-instance stuff is giving me a headache. I understand given this...

    class Foo
      @var = 'bar'
    end
    

    ...that @var is a variable on the created class's instance.

    But how do I create a sub-class overridable class variable?

    Here is an example of what I would do in Python:

    class Fish:
    var = 'fish'
    def v(self):
        return self.var
    
    class Trout(Fish):
        var = 'trout'
    
    class Salmon(Fish):
        var = 'salmon'
    
    print Trout().v()
    print Salmon().v()
    

    Which outputs:

    trout
    salmon
    

    How do I do the same thing in ruby?

    • Jean
      Jean over 14 years
      Shouldn't that be an @@var in the first block of code ?
    • glenn jackman
      glenn jackman over 14 years
      I'm reading "The Well-Grounded Rubyist" (manning.com/black2) by David Black. He does a great job explaining all the nuances in this area
    • docwhat
      docwhat over 14 years
      Jean: No. If I used @@var then subclasses would override the parent classes. See hobodave's link. Specifically how '2' is overriden in the example.
  • docwhat
    docwhat over 14 years
    It's not really, because you have to override the initialize every time you subclass. Which isn't very usable. It may have been better in my example to include some code in initialize.
  • docwhat
    docwhat over 14 years
    No, not quite. You aren't doing a Trout.new or Salmon.new at the end. You're using the class itself. I want the instance to get the class variables.
  • glenn jackman
    glenn jackman over 14 years
    @The Doctor -- how about now?
  • docwhat
    docwhat over 14 years
    Yup. I think it is now functionally equiv. to the answer I gave. You're just writing the accessors manually.
  • docwhat
    docwhat over 14 years
    Typo: fish.foo should be fish.var
  • Dave Sims
    Dave Sims over 14 years
    Actually, no. I didn't define 'foo' but the point was that the Ruby interpreter would see 'fish' as the receiver of the call and set 'self' to 'fish' in order to resolve the reference. I've added the foo method for clarity though. Calling 'fish.var' would throw a NoMethodError.
  • glenn jackman
    glenn jackman over 14 years
    Have to write the accessors: all instance variables are private, and the attr_* methods apply to instance objects not class objects.