Ryan-Neal Mes
Ryan-Neal Mes

Reputation: 6263

Why the module `ClassMethods` defined and extended in the same namespace?

I am trying to understand code from github repo. It's the main module of the gem to setup the client.

module Github
  # more code
  class << self
    def included(base)
      base.extend ClassMethods # what would this be for?
    end
    def new(options = {}, &block)
      Client.new(options, &block)
    end
    def method_missing(method_name, *args, &block)
      if new.respond_to?(method_name)
        new.send(method_name, *args, &block)
      elsif configuration.respond_to?(method_name)
        Github.configuration.send(method_name, *args, &block)
      else
        super
      end
    end
    def respond_to?(method_name, include_private = false)
      new.respond_to?(method_name, include_private) ||
      configuration.respond_to?(method_name) ||
      super(method_name, include_private)
    end
  end

  module ClassMethods
    def require_all(prefix, *libs)
      libs.each do |lib|
        require "#{File.join(prefix, lib)}"
      end
    end
    # more methods ...
  end

  extend ClassMethods
  require_all LIBDIR,
    'authorization',
    'validations',
    'normalizer',
    'parameter_filter',
    'api',
    'client',
    'pagination',
    'request',
    'response',
    'response_wrapper',
    'error',
    'mime_type',
    'page_links',
    'paged_request',
    'page_iterator',
    'params_hash'

end
  1. Why is class << self and module ClassMethods used, and then extended instead of being included in the class << self part?
  2. There is a class method def included(base). This seems to add the class methods into a specific object. Why is it like this? It could relate to the functionality of the class, but I do not understand it.

Upvotes: 27

Views: 12745

Answers (2)

art-solopov
art-solopov

Reputation: 4775

module MyModule
  class << self
    def included(base)
      base.extend ClassMethods # what would this be for?
    end
    <...>
  end
  <...>
end

This is actually a pretty common practice in Ruby. Basically, what it's saying is: when some object performs include MyModule, make it also extend MyModule::ClassMethods. Such a feat is useful if you want a mixin that adds some methods not just to the instances of a class, but to the class itself.

A short example:

module M
  # A normal instance method
  def mul
    @x * @y
  end
 
  module ClassMethods
    # A class method
    def factory(x)
      new(x, 2 * x)
    end
  end
 
  def self.included(base)
    base.extend ClassMethods
  end
end
 
class P
  include M
  def initialize(x, y)
    @x = x
    @y = y
  end
 
  def sum
    @x + @y
  end
end
 
p1 = P.new(5, 15)
puts "#{p1.sum} #{p1.mul}" # <= 20 75

# Calling the class method from the module here!
p2 = P.factory(10)
puts "#{p2.sum} #{p2.mul}" # <= 30 200

Upvotes: 34

Ryan-Neal Mes
Ryan-Neal Mes

Reputation: 6263

Looking more at the repo there is another class Github::API. This class seems to require functionality of the Github::ClassMethods module.

module Github
  # Core class responsible for api interface operations
  class API
    extend Github::ClassMethods

So it makes sense that it's own own module. It gives the ability to only import those methods. If the methods from class << self were included, they would become available which is probably not wanted.

It may have been better to have the module in it's own class or named something else. But I guess that is just personal choice.

Upvotes: 4

Related Questions