Skip to content

Instantly share code, notes, and snippets.

@amit352
Forked from damien-roche/rubymethodlookup.md
Created July 30, 2017 03:09
Show Gist options
  • Save amit352/13471dcc5e2aa4cd2773cf2306e5ed91 to your computer and use it in GitHub Desktop.
Save amit352/13471dcc5e2aa4cd2773cf2306e5ed91 to your computer and use it in GitHub Desktop.

Revisions

  1. @damien-roche damien-roche revised this gist Oct 17, 2014. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion rubymethodlookup.md
    Original file line number Diff line number Diff line change
    @@ -156,7 +156,7 @@ end

    MyCar.new.method
    # => "defined on MyCar"
    "defined on Vehicle"
    # => "defined on Vehicle"
    ```

    ## One More Step
  2. @damien-roche damien-roche revised this gist Oct 17, 2014. 1 changed file with 25 additions and 3 deletions.
    28 changes: 25 additions & 3 deletions rubymethodlookup.md
    Original file line number Diff line number Diff line change
    @@ -124,8 +124,8 @@ Ok, so we can't find the method on the singleton, we can't find it within the cl
    We've reached the end of the ancestors chain and still can't find our method. What now? Ruby will now return back to (1) and run `method_missing`. To give an example:

    ```ruby
    class Vehicle
    def method; 'defined on Vehicle'; end
    class BasicObject
    def method; 'defined on Object'; end
    end

    class MyCar
    @@ -134,7 +134,29 @@ class MyCar
    end
    end

    MyCar.new.method # => 'defined on Vehicle'
    MyCar.ancestors # => [MyCar, Object, Kernel, BasicObject]
    MyCar.new.method # => 'defined on Object'
    ```

    ## Super

    The `super` method looks up the ancestors chain for the method name where `super` is called. It accepts optional arguments which are then passed onto that method. This will follow the same lookup path as outlined above from when the `super` method was called.

    ```ruby
    class Vehicle
    def method; 'defined on Vehicle'; end
    end

    class MyCar < Vehicle
    def method
    "defined on MyCar\n"
    super
    end
    end

    MyCar.new.method
    # => "defined on MyCar"
    "defined on Vehicle"
    ```

    ## One More Step
  3. @damien-roche damien-roche revised this gist Oct 16, 2014. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions rubymethodlookup.md
    Original file line number Diff line number Diff line change
    @@ -172,8 +172,8 @@ Array.ancestors
    Array.superclass
    # => Object

    # This will return all instance methods from the class, included modules, superclasses
    # anything available to the class
    # This will return all instance methods from the class, modules, superclasses
    # (i.e. anything available to the class)

    Array.instance_methods
    # => [:huge, :list, :of, :methods]
  4. @damien-roche damien-roche revised this gist Oct 16, 2014. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions rubymethodlookup.md
    Original file line number Diff line number Diff line change
    @@ -115,9 +115,9 @@ MyCar.new.method # => 'defined on MyCar'
    MyCar.new.method # => 'defined on A'
    ```

    ### 5. Climb the ancestor chain
    ### 5. Climb the ancestors chain

    Ok, so we can't find the method on the singleton, we can't find it within the class definition, and we can't find it within any mixed in modules. What now? Well, we move up the ancestor chain and start from (3). Why not (1) you might ask? That's because the first two lookups involve singletons, whereas your ancestors are just modules/classes, not instances. This means we start with methods on the superclass, then lookup modules mixed into that class, and on we go to the next superclass.
    Ok, so we can't find the method on the singleton, we can't find it within the class definition, and we can't find it within any mixed in modules. What now? Well, we move up the ancestors chain and start from (3). Why not (1) you might ask? That's because the first two lookups involve singletons, whereas your ancestors are just modules/classes, not instances. This means we start with methods on the superclass, then lookup modules mixed into that class, and on we go to the next superclass.

    ### 6. Start again, check method_missing

  5. @damien-roche damien-roche revised this gist Oct 16, 2014. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion rubymethodlookup.md
    Original file line number Diff line number Diff line change
    @@ -1,6 +1,6 @@
    # A Primer on Ruby Method Lookup

    Method lookup is a simple affair in most languages without multiple inheritance. You start from the receiver and move up the inheritance chain until you locate the method. Because Ruby allows you to mix in modules and extend singleton classes at runtime, this is an entirely different affair.
    Method lookup is a simple affair in most languages without multiple inheritance. You start from the receiver and move up the ancestors chain until you locate the method. Because Ruby allows you to mix in modules and extend singleton classes at runtime, this is an entirely different affair.

    I will not build contrived code to exemplify the more complicated aspects of Ruby method lookup, as this will only serve to confuse the matter. If you are having trouble following method lookup in your own programs, it is not because Ruby has strange rules (it does), it is because your code is too tangled.

  6. @damien-roche damien-roche revised this gist Oct 16, 2014. 1 changed file with 2 additions and 0 deletions.
    2 changes: 2 additions & 0 deletions rubymethodlookup.md
    Original file line number Diff line number Diff line change
    @@ -1,3 +1,5 @@
    # A Primer on Ruby Method Lookup

    Method lookup is a simple affair in most languages without multiple inheritance. You start from the receiver and move up the inheritance chain until you locate the method. Because Ruby allows you to mix in modules and extend singleton classes at runtime, this is an entirely different affair.

    I will not build contrived code to exemplify the more complicated aspects of Ruby method lookup, as this will only serve to confuse the matter. If you are having trouble following method lookup in your own programs, it is not because Ruby has strange rules (it does), it is because your code is too tangled.
  7. @damien-roche damien-roche created this gist Oct 16, 2014.
    201 changes: 201 additions & 0 deletions rubymethodlookup.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,201 @@
    Method lookup is a simple affair in most languages without multiple inheritance. You start from the receiver and move up the inheritance chain until you locate the method. Because Ruby allows you to mix in modules and extend singleton classes at runtime, this is an entirely different affair.

    I will not build contrived code to exemplify the more complicated aspects of Ruby method lookup, as this will only serve to confuse the matter. If you are having trouble following method lookup in your own programs, it is not because Ruby has strange rules (it does), it is because your code is too tangled.

    When you pass a message to an object, here is how Ruby finds what method to call:

    ### 1. Look within singleton class

    Simply put, a singleton method is a method that is defined on an instance as oppose to a class where the method would be available on all instances.

    This takes precedence even over methods defined on the class.

    Here is an example to demonstrate:

    ```ruby
    class MyCar
    def method; 'defined on car'; end
    end

    object = MyCar.new

    def object.method
    'defined on singleton'
    end

    object.method # => 'defined on singleton'
    ```

    ### 2. Look within modules that extend singleton

    If we can't find the method on the singleton, we'll take a look in any modules that extend the singleton.

    You can extend singletons like so:

    ```ruby
    module MyModule
    def method; 'defined on MyModule'; end
    end

    object = MyCar.new
    object.extend(MyModule)
    object.method # => 'defined on MyModule'
    ```

    If you extend with multiple modules, later modules take precedence:

    ```ruby
    module MyModule2
    def method; 'defined on MyModule2'; end
    end

    object.extend(MyModule)
    object.extend(MyModule2)
    object.method # => 'defined on MyModule2'
    ```

    ### 3. Look within methods defined on class

    Considering you rarely extend singletons, this is usually where you would start in practice:

    ```ruby
    class MyCar
    def method; 'defined on MyCar'; end
    end

    MyCar.new.method # => 'defined on MyCar'
    ```

    ### 4. Look within modules that were mixed in when class was defined

    So, the method doesn't exist within the class, but we mixed in a bunch of methods from a few modules:

    ```ruby
    module A
    def method; 'defined on A'; end
    end

    module B
    def method; 'defined on B'; end
    end

    class MyCar
    include A
    include B
    end
    ```

    Similar to when we extend a singleton, the later modules take precedence:

    ```ruby
    MyCar.new.method # => 'defined on B'
    ```

    ### Note:

    Ruby 2.0 introduced a `prepend` method to accompany `include`. Methods defined in a prepended module take precedence over methods defined on the class.

    ```ruby
    module A
    def method; 'defined on A'; end
    end

    class MyCar
    prepend A

    def method; 'defined on MyCar'; end
    end

    # with include A
    MyCar.new.method # => 'defined on MyCar'

    # with prepend A
    MyCar.new.method # => 'defined on A'
    ```

    ### 5. Climb the ancestor chain

    Ok, so we can't find the method on the singleton, we can't find it within the class definition, and we can't find it within any mixed in modules. What now? Well, we move up the ancestor chain and start from (3). Why not (1) you might ask? That's because the first two lookups involve singletons, whereas your ancestors are just modules/classes, not instances. This means we start with methods on the superclass, then lookup modules mixed into that class, and on we go to the next superclass.

    ### 6. Start again, check method_missing

    We've reached the end of the ancestors chain and still can't find our method. What now? Ruby will now return back to (1) and run `method_missing`. To give an example:

    ```ruby
    class Vehicle
    def method; 'defined on Vehicle'; end
    end

    class MyCar
    def method_missing(name, *args, &block)
    "called #{name}"
    end
    end

    MyCar.new.method # => 'defined on Vehicle'
    ```

    ## One More Step

    There is a rule that is used to think about the above behaviour called "one step to the right, and up", which can be described as: go right into the receiver's class (singleton), then up the ancestors chain. Here is a simple visualisation to further clarify:

    ````
    Object
    ^
    |
    Parent class A
    ^
    |
    Module included by Parent Class B
    ^
    |
    Parent class B
    ^
    |
    object's class
    ^
    |
    obj -> obj's singelton_class
    ````

    #### BONUS: Helper methods

    Here are a few methods to help you inspect the above in your own applications:

    ```ruby
    Array.ancestors
    # => [Array, Enumerable, Object, Kernel, BasicObject]

    # note: below returns Object because Enumerable is mixed in module (not a superclass)
    Array.superclass
    # => Object

    # This will return all instance methods from the class, included modules, superclasses
    # anything available to the class

    Array.instance_methods
    # => [:huge, :list, :of, :methods]

    # `false` argument below will only return methods defined within the Array class
    Array.instance_methods(false)

    # The following shows where the instance method is defined (Enumerable)
    Array.instance_method(:reduce)
    # => #<UnboundMethod: Array(Enumerable)#reduce>

    # `false` argument below returns methods defined only the singleton

    arr = Array.new
    def arr.new_method; end

    arr.methods(false)
    # => [:new_method]
    ```

    #### References:

    - https://practicingruby.com/articles/method-lookup-1
    - https://practicingruby.com/articles/method-lookup-2
    - http://tech.pro/tutorial/1149/understanding-method-lookup-in-ruby-20
    - http://stackoverflow.com/questions/23848667/ruby-method-lookup-path-for-an-object
    - http://ruby-metaprogramming.rubylearning.com/html/ruby_metaprogramming_2.html