Skip to content

Instantly share code, notes, and snippets.

@robturtle
Last active January 5, 2023 15:53
Show Gist options
  • Select an option

  • Save robturtle/b20c5e1077ef6ab1cb73605aff0d6b1c to your computer and use it in GitHub Desktop.

Select an option

Save robturtle/b20c5e1077ef6ab1cb73605aff0d6b1c to your computer and use it in GitHub Desktop.
Ruby method lookup chain

  1. There are all (instance) methods and no standalone functions in Ruby
    1. Instance methods: methods can only be called from an instance and defined in the class of the instance
    2. methods defined in class objects are bytecodes without binded self object
    3. obj.foo(*args, &block) means obj.class.instance_method(:foo).bind(obj).call(*args, &block)
    4. foo(*args, &block) means self.foo(*args, &block)
  2. All methods are defined inside a Class object
    1. a Class is an object (i.e. an instance of class Class)
    2. classes are singletons (because constants are singletons)
  3. All method lookups strictly follows the lookup chain provided above
    1. super means lookup and invoke the method with the same name and same arguments in the lookup chain one level above
    2. chain from M0 to the very end of the chain is shared across all instances of obj.class . (get from obj.class.ancestors)
    3. chain from obj.singleton_class till obj.class is exclusive for obj (get from obj.singleton_class.included_modules)
    4. the exact inserting place of include, prepend and extend is given above (IMPORTANT: it's from the perspective of obj.class!)
  4. Common tricks
    1. How to change self: instance_eval
    2. How to specify a certain method object to call? obj.to_s v.s. ::Kernel.instance_method(:to_s).bind(obj).call
    3. Where does this method defined? obj.method(:foo).owner #=> of type Module
    4. Method#source_location
    5. Method#source

Common design patterns

class methods

class X
  def X.foo; end
  def self.foo; end # self is X here
  
  class << self # reopen X.singleton_class
    def foo; end
  end
end

def X.foo; end

X.extend(Module.new do
  def foo; end
end)

decorators (overriding functions)

class X
  def foo; :base; end
end

X.prepend(Module.new do
  def foo
    puts "before"
    super
    puts "after"
  end
end)

Q1: How to override a class method?

thread-safe class variables

X.extend(Module.new do
  def class_var
    @class_var ||= []
  end
end)
@x-yuri
Copy link

x-yuri commented Jan 4, 2023

I've made a diagram based on this:

The ruby code:

module MA1; end
module MA2; end
module MAP1; end
module MAP2; end
class A
  include MA1
  include MA2
  prepend MAP1
  prepend MAP2
end

module MB1; end
module MB2; end
module MBP1; end
module MBP2; end
class B < A
  include MB1
  include MB2
  prepend MBP1
  prepend MBP2
end

b = B.new
module S1; end
module S2; end
module SP1; end
module SP2; end
class << b
  include S1
  include S2
  prepend SP1
  prepend SP2
end

p b.singleton_class.ancestors
p B.singleton_class.ancestors

The output:

[SP2, SP1, #<Class:#<B:0x00007f7391609a80>>, S2, S1,
 MBP2, MBP1, B, MB2, MB1,
 MAP2, MAP1, A, MA2, MA1,
 Object, Kernel, BasicObject]
[#<Class:B>, #<Class:A>, #<Class:Object>, #<Class:BasicObject>,
 Class, Module, Object, Kernel, BasicObject]

@x-yuri
Copy link

x-yuri commented Jan 4, 2023

Another one based on Diving into Ruby Singleton Classes article:

@x-yuri
Copy link

x-yuri commented Jan 4, 2023

And another one based on the Class documentation page:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment