2

I've been reading this article on the difference between include & extend in ruby.

If I have this module, I understand how the first and second methods of the module will be used in the class. What I don't understand is how the class << self will be used by include or extend.

module Direction
  def straight
    puts "going straight!"
  end

  def turn
    puts "turning!"
  end

  class << self
    def stop
      puts "stopping!"
    end
  end
end

# This will work because `include` brings them in as instance methods
class Car
  include Direction
end

Car.new.straight
Car.new.turn

# ---------------------
# Now this will also work because `extend` brings them in as class methods
class Car
  extend Direction
end

Car.straight
Car.turn

# ---------------------

Now, the issue is, doing Car.stop or Car.new.stop will always result in an error:

/Users/<name>/Projects/ruby-testing/main.rb:34:in `<main>': undefined method `stop' for Car:Class (NoMethodError)

Why are class methods not carried over via include and extend?


I started thinking about this because of my research into the [forwardable source code at line 119].(https://github.com/ruby/ruby/blob/master/lib/forwardable.rb#L119)

Thank you for any help you may have!


Update from Answer Below

The following was an example given:

module Direction
  def self.included(base)
    base.extend(ClassMethods)
  end

  module ClassMethods
    def stop
      puts 'stopping!'
    end
  end

  def straight
    puts "going straight!"
  end

  def turn
    puts "turning!"
  end
end

class Car
  include Direction
end

This I understand now, and I understand how I can implement class methods from a module into a class using def self.included(base). My question is, if we used extend inside of Car instead of include, would we still be able to get at those class methods using def self.included(base)?

qarthandso
  • 2,100
  • 2
  • 24
  • 40

1 Answers1

3

When you define a method with class << self you are defining a class method. It's the same as defining the methed like this:

class Foo
  def self.foo
    puts 'foo'
  end
  # the above definition is the same as doing:
  class << self
    def foo
      puts 'foo'
    end
  end
end

The above shows 2 ways of defining class methods which are called directly on the class and not on instances of the class. You might use the 2nd syntax if you want to define only class methods or several of them inside of the class << self block. But either style has the same result.

Since you've defined a class method on the Direction module, include or extend will not inherit the class method of that module. This is the expected behavior.

If you want to use inheritance with class methods from a module, you should do it like this which is explained further down in the article you've linked

module Direction
  def self.included(base)
    base.extend(ClassMethods)
  end

  module ClassMethods
    def stop
      puts 'stopping!'
    end
  end

  def straight
    puts "going straight!"
  end

  def turn
    puts "turning!"
  end
end

class Car
  include Direction
end

Now calling class methods on Car will inherit as defined in the Direction class.

Car.stop
stopping!
=>nil # calling a method will return nil unless the method returns a value.

However always be careful using inheritance of any kind as Ruby is a dynamic language. So if you do the above code and then later redefine this method:

module Direction
  module ClassMethods
    def stop
      puts 'go!'
    end
  end
end

Guess what will happen if you do this:

Car.stop

Since the method was defined inside Direction module, when the method gets called on Car it will be calling the method from the Direction module.

Car.stop
go!
=>nil

Updated based on comments:

If you prefer to use extend vs include you would need to do this instead:

module Direction
  def self.extended(base)
    base.extend(ClassMethods)
  end

  module ClassMethods
    def stop
      puts 'stopping!'
    end
  end
end

class Car
  extend Direction
end

In this example, all the methods which were inherited from the module are "copied" to the class extending them. This avoids the problem of possible result of redefining the module method which I warned about when using include previously in my answer.

But you may want to look at answers to this question for ideas about when and why to use either case.

lacostenycoder
  • 10,623
  • 4
  • 31
  • 48
  • Added an update to the question if you wouldn't mind taking a look at it! Wanted to know basically if I'm able to use `extend` instead of `include` with the given explanation you provided. I understand it's a little contrived since the methods coming over would be coming over as class methods anyway. Thus, maybe the update to my question has no bearing, just want to be sure. – qarthandso Oct 19 '19 at 15:26
  • Part of the reason I'm asking the above question is, `forwardable` recommends to include it by way of the `extend` keyword, but the line in the source code that I linked to has a class method defined with the `class << self` manner. This is primarily why I'm asking. If I'm to use `forwardable` via `extend`, but `forwardable` has this `class << self` method in it, am I getting access to that method in any way? – qarthandso Oct 19 '19 at 15:28
  • Thank you greatly for the link. Appreciate the help! – qarthandso Oct 19 '19 at 15:53
  • Good answer. You said, "Since you've defined a class method on the `Direction` module, `include` or `extend` will not inherit the class method of that module." While that is perfectly correct, it may raise a question in qarthandso's mind: "If class methods cannot be inherited by the class, is there any point including them in the module?" You might address that, possibly referencing the `Math` module, which contains nothing but class (really module) methods. Also, I think, "If you want to use inheritance with class methods from a module..." could be better worded. – Cary Swoveland Oct 19 '19 at 18:04
  • @cary please feel free to help me on rewording this? – lacostenycoder Oct 20 '19 at 15:06