Ruby Magick: Include and Extend

Created: 24 September 2014  Modified:

While wandering lost through the strange land of Rails code I stumbled upon many lines of code. One particular bit that crossed my path was the /railties/lib/rails/generators/migration.rb code.

migration.rb

require 'active_support/concern'
require 'rails/generators/actions/create_migration'
module Rails
  module Generators
    # Holds common methods for migrations. It assumes that migrations has the
    # [0-9]*_name format and can be used by another frameworks (like Sequel)
    # just by implementing the next migration version method.
    module Migration
      extend ActiveSupport::Concern
      attr_reader :migration_number, :migration_file_name, :migration_class_name
      module ClassMethods
        def migration_lookup_at(dirname) #:nodoc:
          Dir.glob("#{dirname}/[0-9]*_*.rb")
        end
        def migration_exists?(dirname, file_name) #:nodoc:
          migration_lookup_at(dirname).grep(/d+_#{file_name}.rb$/).first
        end
===== #####code omitted here#################

    end
  end
end

Having never seen “ClassMethods” module in use, I decided to investigate. It turns out that this is a design pattern prevalent in Rails. Confused I decided to investigate using code examples. First we need to understand Class and Instance methods. Class methods are methods that belong to the class and are not available to an object instantiated from the class. Instance methods are just the opposite. They are methods that belong to an instantiated object but not to the class. The following code will illustrate this point.

language/include-extend/expected.rb

class  Expected
  #Putting self in front of the method name makes it a class method
  def self.class_method
    puts "Expected.class_method succeeded!"
  end

  def instance_method
    puts "expected.instance_method succeeded!"
  end

end

puts ""
puts "Expected Code"
puts "Begin using class"
puts "--------------------------------------"
begin
  puts Expected.class_method
  puts Expected.instance_method
rescue
  puts "Expected.instance_method failed!"
  puts ""
end

puts "Begin using instantiated object"
puts "--------------------------------------"
expected = Expected.new

begin
  puts expected.instance_method
  puts expected.class_method
rescue
  puts "expected.class_method failed!"
end

Which when run gives us the following results

run expected.rb in command line

bash-4.2$ ruby expected.rb 

Expected Code
Begin using class
--------------------------------------
Expected.class_method succeeded!

Expected.instance_method failed!

Begin using instantiated object
--------------------------------------
expected.instance_method succeeded!

expected.class_method failed!

Having the basics under our belt let us take another step and talk about include and extend. Include and extend are used to include Modules into classes and other modules. Include “includes” instance methods and extends “includes” class methods. It can actually get quite a bit more complicated than this simple explanation. We will come back to this at the end.

Using include and extend we can write the following:

language/include-extend/using-module.rb

module UsingModule
  module InstanceMethods
    def instance_method
      puts "using_class.instance_method worked!"
    end
  end

  module ClassMethods
    def class_method
      puts "UsingClass.class_method worked!"
    end
  end
end


class UsingClass
  include UsingModule::InstanceMethods
  extend UsingModule::ClassMethods

end

puts ""
puts "Using Module Code"
puts "Begin using class instance"
puts "-------------------------------"
using_class = UsingClass.new()

begin
  puts using_class.instance_method
  puts using_class.class_method
rescue
  puts "using_class.class_method failed!"
end

puts ""
puts "Begin using class"
puts "-------------------------------"

begin
  puts UsingClass.class_method
  puts UsingClass.instance_method
rescue
  puts "UsingClass.instance_method failed!"
end

Which gives us similar results as expected.rb.

run using-module.rb in command line

bash-4.2$ ruby using-module.rb 

Using Module Code
Begin using class instance
-------------------------------
using_class.instance_method worked!

using_class.class_method failed!

Begin using class
-------------------------------
UsingClass.class_method worked!

UsingClass.instance_method failed!

Using-module.rb while an interesting experiment isn’t what you will likely see. You are more likely to see the use of only a ClassMethods sub module along with included class method as shown next.

language/include-extend/confusing-code.rb

module ConfusingModule
  def self.included(base)
=     This commented out code is also seen.
=     The results are the same as the line below it.
=     base.send :extend, ClassMethods
    base.extend(ClassMethods)
  end

  def instance_method
    puts "using_class.instance_method worked!"
  end

  module ClassMethods
    def class_method
      puts "ConfusingClass.class_method worked!"
    end
  end
end


class ConfusingClass
  include ConfusingModule

end

puts ""
puts "Confusing Code"
puts "Begin using class instance"
puts "-------------------------------"
confusing_class = ConfusingClass.new()

begin
  puts confusing_class.instance_method
  puts confusing_class.class_method
rescue
  puts "confusing_class.class_method failed!"
end

puts ""
puts "Begin using class"
puts "-------------------------------"

begin
  puts ConfusingClass.class_method
  puts ConfusingClass.instance_method
rescue
  puts "ConfusingClass.instance_method failed!"
end

Which once again will produce similar results.

Terminal window in mysecurity directory

bash-4.2$ ruby confusing-code.rb 

Confusing Code
Begin using class instance
-------------------------------
using_class.instance_method worked!

confusing_class.class_method failed!

Begin using class
-------------------------------
ConfusingClass.class_method worked!

ConfusingClass.instance_method failed!

Clear as mud? Good! I highly recommend playing with this code to get a more intuitive grasp of how it works. We now proceed to how Rails does all of this with hidden magic. It does this with ActiveSupport::Concern. This next code example requires you to have the ActiveSupport gem installed.

language/include-extend/concerned-code.rb

require 'active_support'

module ConcernedModule
  extend ActiveSupport::Concern

  def instance_method
    puts "concerned_class.instance_method worked!"
  end

  module ClassMethods
    def class_method
      puts "ConcernedClass.class_method worked!"
    end
  end
end


class ConcernedClass
  include ConcernedModule

end

puts ""
puts "Concerned Code"
puts "Begin concerned class instance"
puts "-------------------------------"
concerned_class = ConcernedClass.new()

begin
  puts concerned_class.instance_method
  puts concerned_class.class_method
rescue
  puts "concerned_class.class_method failed!"
end

puts ""
puts "Begin concerned class"
puts "-------------------------------"

begin
  puts ConcernedClass.class_method
  puts ConcernedClass.instance_method
rescue
  puts "ConcernedClass.instance_method failed!"
end

Code which once again produces similar results.

Terminal window in mysecurity directory

bash-4.2$ ruby concerned-code.rb 

Concerned Code
Begin concerned class instance
-------------------------------
concerned_class.instance_method worked!

concerned_class.class_method failed!

Begin concerned class
-------------------------------
ConcernedClass.class_method worked!

ConcernedClass.instance_method failed!

ActiveSupport::Concern, among other things, searches for a ClassMethods sub module and extends it. This brings us to where we can understand a bit more of Rails code and the Ruby magick it employs. Finally lets look at one last piece of code that can confuse the use of extend. What happens if we call extend on an object?

language/include-extend/extend-confusion.rb

module MoreConfusionModule

  def instance_method
    puts "more_confusion_class.instance_method worked!"
  end

  module ClassMethods
    def class_method
      puts "MoreConfusionClass.class_method worked!"
    end
  end
end

module Diabolical
  module ClassMethods
    def confuse
      puts "Haha i am not a class method!"
    end
  end
end



class MoreConfusionClass
  include MoreConfusionModule

end

puts ""
puts "MoreConfusion Code"
puts "Begin more_confusion class instance"
puts "-------------------------------"
more_confusion_class = MoreConfusionClass.new()

more_confusion_class.extend(Diabolical::ClassMethods)


begin
  puts more_confusion_class.instance_method
  puts more_confusion_class.class_method
rescue
  puts "more_confusion_class.class_method failed!"
end

puts ""
puts "Begin more_confusion class"
puts "-------------------------------"

begin
  puts MoreConfusionClass.class_method
  puts MoreConfusionClass.instance_method
rescue
  puts "MoreConfusionClass.instance_method failed!"
end


puts "-------------------------------"
puts more_confusion_class.confuse

The results of running this class may be somewhat suprising.

Terminal window in mysecurity directory

bash-4.2$ ruby extend-confusion.rb 

MoreConfusion Code
Begin more_confusion class instance
-------------------------------
more_confusion_class.instance_method worked!

more_confusion_class.class_method failed!

Begin more_confusion class
-------------------------------
MoreConfusionClass.instance_method failed!
-------------------------------
Haha i am not a class method!

The confuse method defined in Diabolical::ClassMethods mocks us! Extend assigns the methods to the object which is calling it. You do remember that in Ruby classes are also objects? So if you call extend from an object it will assign the methods to the object not the class.

Below are links to sources the first of which I found to be a fantastic resource.

tags: ActiveSupport - ActiveSupport::Concern - extend - include - Rails - Ruby
   Less Is More