Почему модуль `ClassMethods` определен и расширен в одном пространстве имен?

Я пытаюсь понять код из github repo. Это основной модуль драгоценного камня для настройки клиента.

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
  • Почему используются class << self и module ClassMethods, а затем расширены, а не включены в часть class << self?
  • Существует метод класса def included(base). Кажется, это добавляет методы класса в конкретный объект. Почему так? Это может относиться к функциональности класса, но я этого не понимаю.

Ответ 1

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

На самом деле это довольно распространенная практика в Ruby. В основном, что он говорит: когда какой-то объект выполняет include MyModule, сделайте его также extend MyModule::ClassMethods. Такой подвиг полезен, если вы хотите, чтобы mixin добавлял некоторые методы не только к экземплярам класса, но и к самому классу.

Краткий пример:

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}"

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

Ответ 2

Глядя на repo, есть еще один класс Github::API. Этот класс, по-видимому, требует функциональности модуля Github::ClassMethods.

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

Таким образом, имеет смысл, что он имеет собственный модуль. Это дает возможность импортировать только те методы. Если бы были включены методы из class << self, они стали бы доступными, которые, вероятно, не нужны.

Возможно, было лучше иметь модуль в своем классе или назвать что-то еще. Но я думаю, это просто личный выбор.