3

Here is my code:

module Star
  def Star.line
    puts '*' * 20
  end
end

module Dollar
  def Star.line
    puts '$' * 20
  end
end

module At
  def line
    puts '@' * 20
  end
end

include At
Dollar::line # => "@@@@@@@@@@@@@@@@@@@@"
Star::line   # => "$$$$$$$$$$$$$$$$$$$$"
Dollar::line # => "@@@@@@@@@@@@@@@@@@@@"
line         # => "@@@@@@@@@@@@@@@@@@@@"

Can anyone explain how I get this result? I do not understand the method lookup flow here.

sawa
  • 165,429
  • 45
  • 277
  • 381
Nishant Upadhyay
  • 639
  • 7
  • 20

3 Answers3

7

This is how I see it:

Dollar::line

There is no such method defined in this module so It's calling At::line because you included this module.

Star::line

It uses last defining from Dollar module(it goes after original Star definition so it's overridden).

Dollar::line

Third call is the same as the first one.

line

And the last one is At::line because You made an include.

Maxim Pontyushenko
  • 2,983
  • 2
  • 25
  • 36
  • 5
    Almost. Your explanation is correct: `include` at the top-level includes into `Object`, and since `Dollar.is_a?(Object)`, you can call any method which is defined in `Object` or mixed into `Object`, including `At#line`. However, `line` in `At` is an instance method (`At#line`), not a module function (`At::line`), which is precisely why this works. – Jörg W Mittag Jul 09 '15 at 14:45
  • @JörgWMittag Thanks! Your understanding of Module/include/Object things is a way deeper than mine) – Maxim Pontyushenko Jul 09 '15 at 14:55
1

Is

module Dollar
   def Star.line

intentional or is a typo?

Looks like Dollar.line is not defined, and the method line in At is used instead.

Mario Zannone
  • 2,843
  • 12
  • 18
  • This does not provide an answer to the question. To critique or request clarification from an author, leave a comment below their post. – Michel Keijzers Jul 16 '15 at 07:35
  • Well the question was "Can anyone explain how I get this result? ". My answer is "Looks like Dollar.line is not defined, and the method line in At is used instead". What's wrong with that? Anyway, I already upvoted @MaximPontyushenko explanation, which is much more exhaustive than mine :-) – Mario Zannone Jul 16 '15 at 07:46
  • 1
    You are right, my comment looks incorrect (voted +1 for your answer). – Michel Keijzers Jul 16 '15 at 07:49
1

First you need to understand that Ruby looks up constants somewhat similarly to methods. It starts by looking for the constant in the current lexical scope. If it doesn't find the constant there, it goes up one level and looks there, and so on. If it can't find the constant anywhere else, it eventually searches the top level, which is why you can access modules like Kernel from anywhere in your code.

module Star
end
Star.object_id # 20

module Dollar
  Star.object_id # 20. No Star in current scope, so gets the top-level star
end

module At
  module Star
  end
  Star.object_id # 10. There is now a Star in this scope, so we don't get the top-level one
end

The next thing to understand is that methods defined at the top level in Ruby are made instance methods of Object. Since everything in Ruby is an instance of Object, such methods can always be called.

Finally, consider what include does: it takes instance methods from a module and makes them instance methods in the current scope. So if you include something at the top level, all of those methods get added to Object!

So your code is essentially equivalent to this:

module Star
  def self.line
    puts '*' * 20
  end

  # this overwrites the previous definition
  def self.line
    puts '$' * 20
  end
end

# because of the way constants are looked up, the def ends up in Star
module Dollar
end

module At
  def line
    puts '@' * 20
  end
end

# the include does this, so now every object (including Dollar) can call line
def line
  puts '@' * 20
end

# except Star already has its own line method, so the one from Object won't be called for it
Star.line # "$$$$$$$$$$$$$$$$$$$$"
Max
  • 21,123
  • 5
  • 49
  • 71