Fork me on GitHub

Project Notes

The Case of the Missing Method

Or to mix literary metaphors, where in the world do class methods get stored? An investigation into the sublime and elegant design of metaclasses in ruby.

Notes

I heard about Nadia Odunayo’s brilliant talk The Case of the Missing Method: A Ruby Mystery Story from the Chats in the Cupboard podcast.

This particular talk (which is a master class in designing and presenting a talk in its own right) introduced us to the mysterious singleton_class in ruby.

Every Class has an associated Singleton Class, and this is where class methods are defined (not in the class itself).

As Nadia explains, this approach is a good example of the minimalist and elegant design of ruby itself:

  • “class methods” are implemented under the cover as just plain old “instance methods” defined on a Singleton Class.
  • and the Singleton Class is just a plain old class that just happen to be defined as the singleton_class instance variable of the class in question.

This topic is also covered in “Experiment 5-2: Where Does Ruby Save Class Methods?”, page 127 of Ruby Under a Microscope by Pat Shaughnessy.

Nadia Odunayo - The Case of the Missing Method: A Ruby Mystery Story

clip

Exploring the Ideas

See example.rb for code notes from the talk…

$ cat example.rb
#! /usr/bin/env ruby
require 'minitest/autorun'

class Cake
  def initialize(flavour)
    @flavour = flavour
  end

  def tasty?
    @flavour == 'carrot'
  end

  def self.edible?
    true
  end
end

class CaseOftheMissingMethod < Minitest::Test
  def setup
    @carrot_cake = Cake.new('carrot')
    @mud_cake = Cake.new('mud')
  end

  def test_carrot_cakes_are_tasty
    assert @carrot_cake.tasty?
  end

  def test_other_cakes_are_not_tasty
    assert !@mud_cake.tasty?
  end

  def test_all_cakes_are_edible
    assert @carrot_cake.class.edible?
    assert @mud_cake.class.edible?
  end

  def test_instance_methods_are_defined_on_the_class
    assert Cake.instance_methods.include?(:tasty?)
  end

  def test_class_methods_are_not_defined_on_the_class
    assert !Cake.instance_methods.include?(:edible?)
  end

  def test_class_has_superclass_and_singleton_class
    assert_equal 'Cake', Cake.to_s
    assert_equal 'Object', Cake.superclass.to_s
    assert_equal '#<Class:Cake>', Cake.singleton_class.to_s
  end

  def test_class_ancestors_does_not_include_the_singleton_class
    assert_equal [Cake, Object, Minitest::Expectations, Kernel, BasicObject], Cake.ancestors
    assert Cake.ancestors.include?(BasicObject)
    assert !Cake.ancestors.include?(Cake.singleton_class)
  end

  def test_class_methods_are_defined_on_the_singleton_class
    assert Cake.singleton_class.instance_methods.include?(:edible?)
  end
end

Results of course:

$ ./example.rb
Run options: --seed 51067

# Running:

........

Finished in 0.002050s, 3902.4391 runs/s, 6341.4636 assertions/s.

8 runs, 13 assertions, 0 failures, 0 errors, 0 skips

Credits and References

About LCK#248 ruby
Project Source on GitHub Return to the Project Catalog

This page is a web-friendly rendering of my project notes shared in the LittleCodingKata GitHub repository.

LittleCodingKata is my collection of programming exercises, research and code toys broadly spanning things that relate to programming and software development (languages, frameworks and tools).

These range from the trivial to the complex and serious. Many are inspired by existing work and I'll note credits and references where applicable. The focus is quite scattered, as I variously work on things new and important in the moment, or go back to revisit things from the past.

This is primarily a personal collection for my own edification and learning, but anyone who stumbles by is welcome to borrow, steal or reference the work here. And if you spot errors or issues I'd really appreciate some feedback - create an issue, send me an email or even send a pull-request.