Class Variables and Methods

How to pull general functionality and state out to the class level with variables and methods.

Scroll down...

Content

Resources

Comments

Let's zoom away from the instance level and back to the class level for a second. Just like there are instance variables and instance methods, you can also use class variables and class methods. These are behaviors that are so general that there's no reason to apply them to each and every instance of the class. The rule of thumb for deciding whether something should be a "class var/method" or "instance var/method" is really by asking the question "Does this actually care about the internal state of a specific object?" You'll see this in action below.

In this lesson, we'll show you how you can implement these and why you might find them useful. In OOP, class methods especially will pop up quite a bit (though not nearly as much as instance methods).

Class Variables

Class variables, denoted with TWO @@'s, are owned by the class itself so there is only one of them shared among all the instances instead of one per instance. They aren't incredibly common but there are some elements of state that are obviously meant to be shared across all instances.

In this example, we assume that all Vikings start with the same health, so we don't make it a parameter you can pass in:

class Viking
    @@starting_health = 100
    def initialize(name, age, strength)
        @health = @@starting_health
        # ...other stuff
    end
end

Class Variables vs Constants

If you're thinking that class variables seem pretty similar to constants, you're only correct in one fashion. They are similar because all instances have access to them but that's where the similarity stops. The key difference is that class variables can change while constants cannot.

If you've got something that will never and can never change, use a constant. If you might ever change it, stick with a class variable.

Class Methods

What if you want to set up a method that represents general functionality and doesn't actually need the details of a specific instance to run? Sounds like a case for a class method.

You can define a class method one of two ways:

  1. By preceding its name with self (e.g. def self.class_method)
  2. Using the name of the class (e.g. def Viking.class_method) since that's what self is anyway.
# Method 1:  (most common)
class Viking
    def self.count_all
        # code to count up all Vikings
    end
end

# Method 2:  (less common)
class Viking
    def Viking.count_all
        # code to count up all Vikings
    end
end

# IRB
> Viking.count_all
#=> 123

When to Use Class Methods

There are two common situations when a class method makes perfect sense. These are certainly not the only ones, as you'll see when we dive into Rails.

  1. When you're building new instances of a class that have a bunch of known or "preset" features (called "Factory Methods")
  2. When you have some sort of general utility method that should be identical across all instances and won't need to directly access instance variables (called "Utility Methods")

Though we've come up with fancy names to describe these types of class methods, they're really just patterns of use and not some sort of official Ruby syntax.

Factory Methods

The first case is commonly called a Factory Method, and is designed to save you from having to keep passing a bunch of parameters manually to your initialize method.

In this example, we've identified a special kind of Viking called a "warrior" which has lots of preset values. If we're going to be creating lots of warrior Vikings, farmer Vikings, merchant Vikings etc. and each of them has a pretty defined set of preset attributes, it saves us some trouble to just make a helper (factory) method for creating each of these.

Here, we've set up a create_warrior factory method which runs our Viking.new method with a set of randomized preset values:

class Viking
    def initialize(name, health, age, strength)
        # code to initialize
    end

    # Randomness makes for more interesting variations 
    # between warriors
    def self.create_warrior(name)
        age = rand * 20 + 15   # remember, rand gives a random 0 to 1
        health = [age * 5, 120].min
        strength = [age / 2, 10].min
        Viking.new(name, health, age, strength)  # returned
    end
end

# IRB
> sten = Viking.create_warrior("Sten")
#=> #<Viking:0x007ffc05a79848 @age=21.388120526202737, @name="Sten", @health=106.94060263101369, @strength=10>

Note that it's pretty handy of IRB to list out the instance variables for you after you've created a class. It's almost identical to the output if you were to type sten.inspect. inspect outputs a string so it escapes any quote characters within it (visible in the @name field):

> sten.inspect
#=> "#<Viking:0x007ffc05a79848 @age=21.388120526202737, @name=\"Sten\", @health=106.94060263101369, @strength=10>"

Utility Functions

The second case for class methods is more mundane. Often, there are things you need all Vikings to "know" or be able to do:

class Viking
    ...
    def self.random_name      # useful for making new warriors!
        ["Erik","Lars","Leif"].sample
    end
    def self.silver_to_gold(silver_pieces)
        silver_pieces / 10
    end
    class << self           # The less common way
        def gold_to_silver(gold_pieces)
            gold_pieces * 10
        end
    end
end

# IRB
> warrior1 = Viking.create_warrior(Viking.random_name)
#=> #<Viking:0x007ffc05a745c8 @age=22.369775138257097, @name="Lars", @health=111.84887569128549, @strength=10>

This is also potentially useful for class-wide operations like the Viking.count_all function we used previously.

Again, this is where the rule of thumb comes in. If you find yourself writing a method that doesn't actually use any unique state from a particular object (its instance variables), it should probably be a class method not an instance method.

Class Methods Can't Even Access Instance Vars

It's worth noting that class methods, because they're called by a class and not an instance, obviously cannot access any instance variables or instance methods. This should make sense since, when you call a class method, that method has no way of knowing which instance you might be referring to.

If you wanted to, you could pass an instance as an input to a class method (e.g. Viking.send_to_army(some_viking_instance)) but you'd have to ask yourself whether it would be better as an instance method in that case.

Class methods can access other class methods.

Though class methods cannot access instance variables, instance methods can access both class methods and class variables. This should make sense because, of course, an instance of a class will be able to "see" everything that's class-wide.

Code Review

The important bits of code from this lesson

# Class variable (accessible across instances)
@@starting_health = 100

# Class method
def self.count_all
  # code to count up all Vikings
end

# Call a class method
Viking.count_all

Wrapping Up

Most of your time in OOP will be spent wrangling classes, instance variables and instance methods. The class methods (and occasionally class variables) are often more of a supporting player in that process because they tend to rep more general functionalities. As we dig deeper into OOP, though, you'll find plenty of occasion to use all of these.

One of the key things to understand is the difference between instance variables and methods and class variables and methods. If you've got that, then you have a good understanding of what's going on here. If not, go back and re-read the last few lessons before moving on.



Sign up to track your progress for free

There are ( ) additional resources for this lesson. Check them out!

There are no additional resources for this lesson just yet!

Sorry, comments aren't active just yet!

Next Lesson: Storing Classes in Multiple Files