In Progress
Unit 1, Lesson 1
In Progress

Class Method

Video transcript & code

In Ruby, there are no such things as class methods.

This statement may come as something of a surprise. After all, if you've worked with other object-oriented languages, you're probably used to them having both instance methods and class methods. Moreover, you might well object that you write Ruby class methods every day. For that matter, on this show I've used the term "class method" repeatedly!

Nonetheless, it's true. There's only one kind of method in Ruby: the instance method. Class methods don't exist.

To understand how this could be, let's start by reviewing some code from episode #453.

We have a Cow class.

We create a new Cow instance, named cletus.

Cows have an instance method, speak.

When we send the speak message, Ruby finds this instance method by looking it up on the class of the object we've named cletus.

Now we give cletus the Cow a special ability: we give him happy feet.

We can now send the dance message to cletus.

This is what we call a "singleton method" in Ruby. It exists only for this specific instance of the Cow class, and no other.

But what we said in that episode is that Ruby always looks up methods in classes. A method is never actually contained "within" an object.

So, since the dance method isn't defined on the Cow class, what class does Ruby find it in?

In addition to its class, the single Cow object named cletus has a singleton_class.

It finds the method in cletus the object's singleton_class.

class Cow
  def speak
    "moo"
  end
end

cletus = Cow.new

cletus.speak                    # => "moo"

cletus.class.instance_methods(false)
# => [:speak]

def cletus.dance
  "tappety tappety tap"
end

cletus.dance                    # => "tappety tappety tap"

cletus.singleton_class
# => #<Class:#<Cow:0x0055ec2c0d4f58>>

cletus.singleton_class.instance_methods(false)
# => [:dance]

OK, let's clear away Cletus the dancing cow for now.

And just as we created a Cow object, let's now create an instance of the Class class.

We'll assign the new object to the variable cow_class.

And we'll fill in the class definition to be the same as our original Cow class.

Now we decide we want to have a way to remember the proper plural term for a group of cows. Remember, the class of this object is Class. We don't want the global ruby Class class to know the plural term for cows. We just want cow_class to have this knowledge.

So we define a singleton method on the cow_class object that returns the term "herd".

We can send this message to the instance, and get the expected response.

This should all be pretty familiar so far. We have an object, and we have defined a singleton method… just like the dance method that we defined on cletus the cow.

Just like cletus, this object has its own singleton_class.

Just like cletus, this object keeps its singleton method definitions in the singleton class.

cow_class = Class.new do
  def speak
    "moo"
  end
end

def cow_class.plural_term
  "herd"
end

cow_class.plural_term
# => "herd"

cow_class.singleton_class
# => #<Class:#<Class:0x0055ffc31d1870>>

cow_class.singleton_class.instance_methods(false)
# => [:plural_term]

Now let's rewrite this code a little bit. Instead of a local variable, let's assign the new Class instance to a constant.

The object's singleton class now formats itself a little differently, but other than that everything works the same.

There's still a plural_term singleton method.

It is still found in the object's singleton_class.

Cow = Class.new do
  def speak
    "moo"
  end
end

def Cow.plural_term
  "herd"
end

Cow.plural_term
# => "herd"

Cow.singleton_class
# => #<Class:Cow>

Cow.singleton_class.instance_methods(false)
# => [:plural_term]

Now let's rewrite a little more. We'll move the definition of the plural_term singleton method from outside the class definition block, to inside it. Since, inside the block, the Cow class is the current object, we also change from Cow.plural_term to self.plural_term.

When we execute this code, we see that everything still works exactly as before.

Cow = Class.new do
  def self.plural_term
    "herd"
  end

  def speak
    "moo"
  end
end

Cow.plural_term
# => "herd"

Cow.singleton_class
# => #<Class:Cow>

Cow.singleton_class.instance_methods(false)
# => [:plural_term]

Let's make one more change. Instead of using Class.new, we'll use Ruby's idiomatic syntax sugar for defining classes.

After running the code, we see that we still haven't really changed anything. The code is different, but the outcome is the same.

There's still a singleton method. There's still a singleton class. And the method is still looked up within this singleton class.

class Cow
  def self.plural_term
    "herd"
  end

  def speak
    "moo"
  end
end

Cow.plural_term
# => "herd"

Cow.singleton_class
# => #<Class:Cow>

Cow.singleton_class.instance_methods(false)
# => [:plural_term]

The code we have in front of us now is a typical Ruby class definition. And the herd method is what we would usually call a "class method".

But what we've seen over the last few transformations is that really, this method is just another object singleton method. It's just that instead of being defined on an an object of class Cow or Shape or User, it's defined on an object of class Class.

If you're used to a language like Java, C++, or C#, you may be used to class methods having special semantics. Let's see if this Ruby so-called "class method" has any of these special abilities.

Let's write a new Cow instance method called listen.

In some languages, class methods can be called directly inside of instance methods, without any special qualification or prefixing. Let's fill in this method as if this is the case. We'll use the plural_term method as if it is just another instance method.

Now let's instantiate a Cow object and send the listen message.

The outcome is an error. Ruby can't find the plural_term method.

class Cow
  def self.plural_term
    "herd"
  end

  def speak
    "moo"
  end

  def listen
    "I heard the #{plural_term}" # ~> NameError: undefined local variable or method `plural_term' for #<Cow:0x0055a3fe586130>
  end
end

Cow.new.listen
# =>

# ~> NameError
# ~> undefined local variable or method `plural_term' for #<Cow:0x0055a3fe586130>
# ~>
# ~> xmptmp-in12195nEf.rb:11:in `listen'
# ~> xmptmp-in12195nEf.rb:15:in `<main>'

In order to make this code work, we have to explicitly send the plural_term message to the current object's class object.

class Cow
  def self.plural_term
    "herd"
  end

  def speak
    "moo"
  end

  def listen
    "I heard the #{self.class.plural_term}"
  end
end

Cow.new.listen
# => "I heard the herd"

So we can see that in Ruby, instance methods don't get any special easy access to class methods. They have to send a message to an explicit object just like they do to any other external collaborator object.

Another special rule that some languages have for class methods is that they can access the private interface of class instances.

For instance, let's give Cow objects a private color attribute.

And then let's define a new class object singleton method called new_brown which will instantiate a new Cow, set its color to brown, and then return it.

Now let's try to create a new Cow using this method.

The result is a complaint that a private method was called on our Cow instance.

class Cow
  def self.plural_term
    "herd"
  end

  def self.new_brown
    cow = new
    cow.color = "brown" # ~> NoMethodError: private method `color=' called for #<Cow:0x0055c8c56ad4c8>
    cow
  end

  def speak
    "moo"
  end

  def listen
    "I heard the #{self.class.plural_term}"
  end

  private

  attr_accessor :color
end

Cow.new_brown
# =>

# ~> NoMethodError
# ~> private method `color=' called for #<Cow:0x0055c8c56ad4c8>
# ~>
# ~> xmptmp-in12195CvO.rb:8:in `new_brown'
# ~> xmptmp-in12195CvO.rb:25:in `<main>'

The only way to make this work without deliberately breaking Ruby's privacy protection is to make the color attribute public.

class Cow
  def self.plural_term
    "herd"
  end

  def self.new_brown
    cow = new
    cow.color = "brown"
    cow
  end

  def speak
    "moo"
  end

  def listen
    "I heard the #{self.class.plural_term}"
  end

  attr_accessor :color
end

Cow.new_brown
# => #<Cow:0x0055bda64512f8 @color="brown">

So we can see that in Ruby, so-called "class methods" also don't have any special access to class instances. They have to respect public/private boundaries just like any other external method.

And the reason is that, like I said at the beginning of this episode, there is no such thing as a class method in Ruby. Not in the sense that other languages have class methods with special rules and treatment.

In Ruby, all there are, are instance methods. Some instance methods are defined in ordinary classes. Others are defined in singleton classes specific to a single object. And when that single object happens to be an object of class Class, we call it a "class method".

But it has no special behavior. No special access. No special rules. It's just another instance method.

Realizing this will take you a long way towards fully grasping Ruby's underlying object model. And it'll help you understand why class object singleton methods don't behave the way class methods do in other languages.

Now, it's annoying to say "class object singleton method" all the time. So I'm going to continue to use the term "class method", as a shorthand to mean the same thing.

But from now on, remember that when we say "class method" in Ruby, we really mean an instance method defined in a class object's singleton class. It may not be the most obvious concept in the world, but if you internalize it, it will save you from setting up incorrect expectations.

Happy hacking!

Responses