22 May 2009

Ruby Object Model

These are my notes from Dave Thomas's Ruby Object Model.

# Ruby Object Model
# http://www.engineyard.com/blog/community/scotland-on-rails/page-2/

# everything is an object
# object is a class
# class is an object

# self:
#  - current object
#  - where instance vars are found
#  - default receiver for methods calls

# changes in self changes ruby's view of the universe
# by: methods calls & class/module definitions

animal = "cat"
puts animal.upcase

# find animal object -> find table of methods -> call method
# class => object that has a table of methods
# every single method call works exactly the same way

puts animal.object_id
# if can't methods in 1st table => goes to parent 
# (superclass of the object)

# Object toplevel in Ruby 1.8, BasicObject in Ruby 1.9
# If no matching methods found in ruby, you are back
# to original object and look for method_missing method.

def animal.speak
  puts "meaow"
end

animal.speak

# --- SELF changes on method call

# Method lookup -> one right and one up
# So the above object instance method sits 
# one to the right of that particular object.
# So a newly created class just for the animal
# object is anonymous. But when asked what class
# the above object belongs to, it says string,
# and not the newly created anonymous class.
# AKA, singleton class or metaclasss or eigenclass
# How is the animal.speak method defined?
# (1) Ruby sets "self" to the cat string object -- self
#     set to the receiver
# (2) Looks for the method in the method table associated
#     with that object (look in self's class for the object)
# (3) If not found, scan for method_missing
# (4) Eventually find some method & invoke it & when done,
#     restores original value of self.
#
# If a method call in Ruby doesn't have an explicit
# receiver -- then Ruby doesn't reset self -- self
# stays the same & no need to pop back at the end.

# So the method "puts" is a private method in Object (actually
# in Kernel), so you can only call it with an implicit receiver,
# not an explicit one.

class String
  puts "in string"
end

begin
  "hi".puts
rescue NoMethodError
  puts "puts is private"
end

# In Ruby class definitions are executable code.
puts "===class defn"
puts "toplevel #{self}"
class << self
  puts "class << self's #{self}"
end
class A
  puts "class A's #{self}"
end
module B
  puts "module B's #{self}"
end

# When you define a class like "class A; end",
# Ruby creates a brand new class and assigns that
# to the constant A.

# When excuting a class or module definition, self
# is set to the class or module object. Why? You can
# do "def self.a" for class methods (or old school
# ruby programmers using "def A.a"); But ruby doesn't
# have class methods (or static methods), all methods
# are the same. These methods are just defined on the class.
# It inserts a singleton class on that particular class object.

# To do metaprogramming in Ruby:
# - Instance variables are looked up in self
# - Methods are looked up in self's class

# these 2 are the same:
def animal.do
  puts "doing"
end
# "class << animal" means open up the singleton class of this object
# self is set to that singleton class
class << animal
  puts "class << animal: #{self}"
  def do
    puts "doing"
  end
end

class N
  # opening up the meta/singleton class and dumping all the
  # methods defined there
  class << self
    # class methods
  end
end

# This doesn't work
class F
  @a = 10 # self is class object
  def f
    @a # self is the instance 
  end
  def F.f # this work
    @a
  end
  
  class << self # this also works
    attr_accessor :f
  end
  
end

class Y; end
class Z < Y; end
# the Y in "Z < Y" is an expression
wxyz = Y
class Z < wxyz; end # same as above

a = Struct.new(:a, :b, :c) # creates a class object on the fly
puts a.new(1,2,3) 
# Struct is just a data holder

class T < Struct.new(:a, :b)
end

# include Module in a class, then invoke the instance methods
# of the module with class as the receiver
# It doesn't add the methods; any instance methods define
# in class overrides the module methods, even if it is included
# after the method definition.

module M
  def speak; puts :no; end
end

class AM
  def speak; puts :ok;  end
  include M
end

am = AM.new.speak

# include => singleton as immediate parent of my class
# and its methods are methods of my module

# hierarchy => singleton, class, module