Search code examples
rubyrubocop

How to correct `Style/ClassVars` in RuboCop?


I see the issue with using class variables with Ruby; however, it seems RuboCop's documentation for how to fix the issue is not sufficient.

Now, I could just ignore it. Given my project, it doesn't matter. But, I just want to know what Rubocop is trying to tell me to do, because it doesn't make sense.

Executing the provided code in irb 0.9.6 with Ruby 2.5.1 gives:

class A
  @test = 10
end
#=> 10
class A
  def test
    @@test # you can access class variable without offense
  end
end
#=> :test
A.new.test
Traceback (most recent call last):
        3: from /Users/Ricky/.rbenv/versions/2.5.1/bin/irb:11:in `<main>'
        2: from (irb):12
        1: from (irb):9:in `test'
NameError (uninitialized class variable @@test in A)
Did you mean?  @test

So, no. We obviously cannot access class variable without offense. irb was very offended. But, ruby suggests using @test. Maybe it was just a typo? Let's try it:

class A
  @test = 10
  def test
    @test # you can access class variable without offense
  end
end
#=> :test
A.new.test
#=> nil

So, the instance variable was never defined. What is RuboCop trying to say here?


Solution

  • You are missing the difference between the scopes of variables.

    class A
      @test = 42
    end
    

    The above declares an instance variable in the class scope. It is accessible as

    A.instance_variable_get(:@test)
    #⇒ 42
    

    You can define an accessor for this variable:

    class A
      @test = 42
      def self.test
        @test
      end
    end
    
    A.test #⇒ 42
    

    It is shared between instances and to access it from instances you should refer to the class:

    #     ⇓⇓⇓⇓⇓ HERE
    A.new.class.test #⇒ 42
    

    The following code declares an instance variable on the class instances:

    class A
      def initialize
        @test = 42
      end
    end
    

    It can be accessed from the instances of A:

    A.new.instance_variable_get(:@test)
    #⇒ 42
    

    Class variables have some drawbacks when used within the class hierarchy, that is [probably] why Rubocop suggests not to use class variables (or whatever it suggests—I honestly never used it since it brings more harm than help IMSO.)

    In your first snippet you have missed the @. The correct code would be:

    class A
    # ⇓⇓ HERE
      @@test = 10
    end
    class A
      def test
        @@test # you can access class variable without offense
      end
    end