ab11 ab11 - 3 months ago 21
Ruby Question

Are Ruby class variables similar to the Java static variables?

I am transitioning to Ruby from Java and trying to understand Ruby scoping.

It seems class variables behave the same as static variables in Java. Is this interpretation correct? If so, are Ruby global variables then equivalent to public static variables in Java?

Answer

There's a lot of similarity between Ruby and Java by virtue of them being object-oriented, but their family tree is different. Ruby leans very heavily on Smalltalk while Java inherits from the C++ school of thinking.

The difference here is that Ruby's concept of public/private/protected is a lot weaker, they're more suggestions than rules, and things like static methods or constants are more of a pattern than a construct in the language.

Global variables are frowned on quite heavily, they can cause chaos if used liberally. The Ruby way is to namespace things:

$ugly_global = 0  # Not recommended, could conflict with other code
                  # Ownership of this variable isn't made clear.

$ugly_global += 1 # Works, but again, it's without context.

module UglyCounter    # Defines a module/namespace to live in
  def self.current    # Defines a clear interface to this value
    @counter ||= 0    # Initializes a local instance variable
  end

  def self.current=(v)   # Allow modification of this value
    @counter = v.to_i    # A chance to perform any casting/cleaning
  end
end

UglyCounter.current += 1   # Modifies the state of a variable, but
                           # the context is made clear.

Even a thin layer like this module gives you the ability to intercept read/write operations from this variable and alter the behaviour. Maybe you want to default to a particular value or convert values into a normalized form. With a bare global you have to repeat this code everywhere. Here you can consolidate it.

Class variables are a whole different thing. They're also best avoided because sharing data between the class and instances of this class can be messy. They're two different contexts and that separation should be respected.

class MessyClass
  @@shared = 0

  def counter
    @@shared
  end

  def counter=(v)
    @@shared = v
  end
end

This is a pretty rough take on how to use a shared class-level instance variable. The problem here is each instance is directly modifying it, bypassing the class context, which means the class is helpless. This is fundamentally rude, the instance is over-extending its authority. A better approach is this:

class CleanerClass
  def self.counter
    @counter ||= 0
  end

  def self.counter=(v)
    @counter = v.to_i
  end

  # These are reduced to simple bridge methods, nothing more. Because
  # they simply forward calls there's no breach of authority.
  def counter
    self.class.counter
  end

  def counter=(v)
    self.class.counter = v
  end
end

In many languages a static class method becomes available in the scope of an instance automatically, but this is not the case in Ruby. You must write bridge/proxy/delegate methods, the terminology here varying depending on what you're used to.