»

ruby: the difference between extend and include

Ruby provides a great platform for modular coding. You’re not limited to just class hierarchies that other languages provide. Through the power of mixins, code can be reused in many robust ways. The two primary ways to utilize the power of mixins are through Ruby’s extend and include.

I know this one has been done many times before, but I felt some further elaboration would help. There are many use cases where extend and include are relevant. I’m also writing this down so I won’t forget it later :-). Let’s dive in.

high level

As a general rule of thumb, you use extend for class level methods, and include for instance level methods. This isn’t always the case, as we’ll see in a later example. For now, let’s just assume this to be the true. Here’s an example that demonstrates this:

module SayHello
  def hello
    puts 'hello'
  end
end

module SayBye
  def bye
    puts 'bye'
  end
end

class Greeter
  include SayHello
  extend SayBye
end

Greeter.bye # class level
bye
=> nil
Greeter.new.hello # instance level
hello
=> nil

This demonstrates the simple way to handle the difference between instance and class level methods. More often than not, you may want a module to be able to have instance AND class level methods, so you can just mix that module in, and have the class gain both. This is possible with this slightly more advanced example:

common mixin usage

module SayHello
  def self.included(base)
    base.extend SayBye
  end

  module SayBye # all class level methods
    def bye # class level method
      puts 'bye'
    end
  end

  def hello # instance level method
    puts 'hello'
  end
end

class Greeter
  include SayHello
end

Greeter.bye # class level
bye
=> nil
Greeter.new.hello # instance level
hello
=> nil

This acheives the same effect as the first example, but with a more simple interface. Instead of having to extend and include 2 seperate modules when you include SayHello, it automatically extends the sub-module SayBye. This is accomplished using the self.included method, which rails calls whenever a module is included. From inside this method, we capture the base object and tell it to extend our class level methods in the SayBye module. In our example the base object is the Greeter class.

extending existing objects

If you have an existing object, whether it be a class or an instance of a class you can use the extend method to mixin a module’s methods at the class or instance levels. This sounds confusing and contradictory to what I just told you, but I think an example will help clarify:

module SayHello
  def hello
    puts 'hello'
  end
end

class Greeter
end

For the sake of this example, I have a module with some methods, in this case the ‘hello’ method. I also have a class, Greeter which I want to gain those methods. Let’s say I do the following.

>> g = Greeter.new
>> g.hello
NoMethodError: undefined method `hello' for #<Greeter:0x00000002319718>
    from (irb):3
    from /usr/bin/irb:12:in `<main>'
>> g.extend SayHello
=> #<Greeter:0x00000002319718>
>> g.hello
hello
=> nil

In this case, I used extend to add the hello method to an already instantiated Greeter object. In this case my g object gained some instance methods unique to that object. If I instantiated a different copy of my Greeter object, it would not have the ‘hello’ method on it. Similarly, extend can be used on the class itself for it to gain class level methods:

>> Greeter.hello
NoMethodError: undefined method `hello' for Greeter:Class
        from (irb):2
        from /usr/bin/irb:12:in `<main>'
>> Greeter.extend SayHello
=> Greeter
>> Greeter.hello
hello
=> nil

The important thing to take away from these examples is that extend works at the level of the type of object you’ve passed it.

include before class is instantiated

Given the same code as above:

module SayHello
  def hello
    puts 'hello'
  end
end

class Greeter
end

Let’s say we want to mixin the hello method to any new instances of Greeter. We can do it like this:

>> Greeter.send :include, SayHello
=> Greeter
>> g = Greeter.new
=> #<Greeter:0x00000002a0bde0>
>> g.hello
hello
=> nil

Remember, use include to have a class gain instance methods before it’s instantiated. Use extend if you want the object to gain instance methods on an already instantiated/existing object. This can be confusing at first, but once you understand what’s going on under the hood, it’s not so bad.

conclusion

I’ve covered all the basic areas where you’d want to use mixins. Hopefully going forward using modules in Ruby will be easier for you and you’ll be able to enjoy coding more with the ability to make your code more modular.

Happy coding!



about the author

Blake Smith is a Principal Software Engineer and leads the Infrastructure group at at Sprout Social.

Blake Smith

create. code. learn.