qarthandso
qarthandso

Reputation: 2190

Ruby include/extend Module: a class method - Beginner

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)?

Upvotes: 2

Views: 155

Answers (1)

lacostenycoder
lacostenycoder

Reputation: 11186

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.

Upvotes: 3

Related Questions