# 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