Skip to content

Instantly share code, notes, and snippets.

@jrust
Last active December 8, 2016 17:12
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jrust/5963217 to your computer and use it in GitHub Desktop.
Save jrust/5963217 to your computer and use it in GitHub Desktop.
An interactive tutorial on the self, singleton class, and method hierarchy in ruby.
# Resources:
# Method lookup diagram: http://phrogz.net/RubyLibs/RubyMethodLookupFlow.pdf
# Seeing metaclasses clearly: http://ruby-metaprogramming.rubylearning.com/html/seeingMetaclassesClearly.html
require 'rubygems'
require 'pry-debugger'
require 'awesome_print'
# Objects hold data, classes hold methods
# Just take a look at the source for Class
# struct RClass {
# struct RBasic basic;
# struct st_table *iv_tbl;
# struct st_table *m_tbl;
# VALUE super;
# };
# It has a table of methods and a pointer to the superclass
#
# And an object just as a table of instance vars:
# struct RObject {
# struct RBasic basic;
# struct st_table *iv_tbl;
# };
class Dog
attr_accessor :name
def initialize(name)
self.name = name
end
def who_is_self
self
end
end
fido = Dog.new 'fido'
ap "-> fido.inspect # So our data is on the object, but where's that name accessor?"
ap fido.inspect
ap "-> fido.singleton_class # An instance of Class with fido as its argument. Also known as metaclass or eigenclass."
ap fido.singleton_class
ap "-> fido.singleton_class.instance_methods(false) # nope, nothing here"
ap fido.singleton_class.instance_methods(false)
ap "-> fido.singleton_class.ancestors # The singleton class inherits all the way up to BasicObject"
ap fido.singleton_class.ancestors
ap "-> fido.singleton_class.superclass # And it's parent is Dog"
ap fido.singleton_class.superclass
ap "-> fido.singleton_class.superclass.class # And as we'd expect, it's a Class"
ap fido.singleton_class.superclass.class
ap "-> fido.singleton_class.superclass.instance_methods(false) # And as we learned above, only Class can hold methods"
ap fido.singleton_class.superclass.instance_methods(false)
ap "-> fido.who_is_self # And who is the self for this object? Just the receiver, our dog Object"
ap fido.who_is_self
binding.pry
# So, what's the point of fido.singleton_class if it's empty?
# so that this instance of dog can have methods added only to it.
# Of course there are lots of ways to achieve this:
# Use the "open it up" syntax
class << fido
def bark; "I'm #{@name}" end
end
# Define it on the object
def fido.bark; "I'm #{@name}" end
ap fido.bark
ap "-> fido.singleton_class.instance_methods(false) # There it is, in this objects singleton class"
ap fido.singleton_class.instance_methods(false)
binding.pry
# But there's another way, a little more common: Modules
# because modules are just bags of methods:
module Bark
def bark; "I'm #{@name}" end
end
lassie = Dog.new 'lassie'
lassie.extend Bark
ap "-> lassie.singleton_class.ancestors # And extend simply adds a new ancestor to the singleton_class"
ap lassie.singleton_class.ancestors
ap lassie.bark
binding.pry
# Ok, then what does include do?
module Wag
def wag; "wagging for #{@name}" end
end
# The same thing, but it is a method of Class, so this won't work, because dog is an Object
# lassie.send :include, Wag # gives NoMethodError
# But this will work, because Dog inherits from Class
Dog.send :include, Wag
ap "-> Dog.ancestors # There is our Wag"
ap Dog.ancestors
ap "-> fido.singleton_class.ancestors # And now our object has both Bark and Wag"
ap fido.singleton_class.ancestors
ap fido.wag
binding.pry
# So we've figured out instance methods, but then how do class methods work?
# Exact same way, because remember every class is an instance of Class, and
# instances have a singleton_class
ap "-> Dog.singleton_class # An instance of Class with Dog as it's argument"
ap Dog.singleton_class
binding.pry
# And as before there are lots of ways we can add methods to Dog's singleton_class,
# thereby creating class methods:
module Breeds
def breeds; [:collie, :poodle, @extra] end
end
def Dog.breeds; [:collie, :poodle, @extra] end
class Dog
@extra = :lab
# Remember, extend adds ancestors to the singleton_class, so this is equivalent to
# self.send :include, Breeds
extend Breeds
def self.breeds; [:collie, :poodle, @extra] end
class << self
def breeds; [:collie, :poodle, @extra] end
end
end
ap "-> Dog.singleton_class.instance_methods(false) # Also as before, the method lives in the singleton_class"
ap Dog.singleton_class.instance_methods(false)
ap "-> fido.singleton_class.ancestors # And Breeds was added to the lookup list"
ap Dog.singleton_class.ancestors
ap "-> Dog.instance_variable_get(:@name) # And since Dog is an instance of Class, and thus an object, it can have data and we can have class variables"
ap Dog.instance_variable_get(:@name)
ap Dog.breeds
binding.pry
# And hopefully this makes sense out of modules as well:
ap "-> Breed.class # They are instance of the Module class"
ap Breeds.class
ap "-> Breed.ancestors # And Module inherits from Class"
ap Breeds.ancestors
ap "-> Breed.singleton_class # As an instance of Module they have a singleton_class"
ap Breeds.singleton_class
module Breeds
# So you can instance variables to the singleton class
@shows = [1, 2]
# And you can extend the singleton class with a new ancestor, self, which is the same as `extend Breed`
extend self
def shows; @shows end
# Or you can define a method directly on the metaclass
def self.show_list; @shows.join(',') end
# Or you can create a module_function which makes it private and copies it to the singleton class
def reverse_list; @shows.reverse.join(',') end
module_function :reverse_list
end
ap Breeds.singleton_class.ancestors
ap Breeds.singleton_class.instance_methods(false)
binding.pry
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment