Как Вы расширяете модуль Ruby с помощью подобных макросу методов метапрограммирования?

Рассмотрите следующее расширение (шаблон популяризированный несколькими плагинами направляющих за эти годы):

module Extension
  def self.included(recipient)
    recipient.extend ClassMethods
    recipient.send :include, InstanceMethods
  end

  module ClassMethods
    def macro_method
      puts "Called macro_method within #{self.name}"
    end
  end

  module InstanceMethods
    def instance_method
      puts "Called instance_method within #{self.object_id}"
    end
  end
end

Если Вы хотели выставить это каждому классу, можно сделать следующее:

Object.send :include, Extension

Теперь можно определить любой класс и использовать макро-метод:

class FooClass
  macro_method
end
#=> Called macro_method within FooClass

И экземпляры могут использовать методы экземпляра:

FooClass.new.instance_method
#=> Called instance_method within 2148182320

Но даже при том, что Module.is_a?(Object), Вы не можете использовать макро-метод в модуле.

module FooModule
  macro_method
end
#=> undefined local variable or method `macro_method' for FooModule:Module (NameError)

Это верно даже при явном включении оригинала Extension в Module с Module.send(:include, Extension).

Для отдельных модулей можно включать расширения вручную и получить тот же эффект:

module FooModule
  include Extension
  macro_method
end
#=> Called macro_method within FooModule

Но как Вы добавляете подобные макросу методы ко всем модулям Ruby?

6
задан Ian Terrell 10 June 2010 в 22:38
поделиться

1 ответ

Рассмотрим следующее расширение (шаблон, популяризированный несколькими плагинами Rails на протяжении многих лет)

Это не шаблон, и он не был «популяризирован» . Это антипаттерн , который был загружен 1337 PHP h4X0rZ, не знающим Ruby. К счастью, многие (все?) Экземпляры этого анти-паттерна были удалены из Rails 3 благодаря жесткому слову Иегуды Каца, Карла Лерша и других. Иегуда даже использует почти тот же самый код, который вы разместили в качестве анти-примера как в своих недавних разговорах об очистке кодовой базы Rails, так и он написал целую запись в блоге только об этом анти-шаблоне.

Если вы хотите предоставить это каждому классу, вы можете сделать следующее:

 Object.send: include, Extension

Если вы все равно хотите добавить его в Object , то почему бы просто не сделать это:

class Object
  def instance_method
    puts "Called instance_method within #{inspect}"
  end
end

Но как добавить методы, подобные макросам, во все модули Ruby?

Просто : добавив их в Модуль :

class Module
  def macro_method
    puts "Called macro_method within #{inspect}"
  end
end

Все просто работает:

class FooClass
  macro_method
end
#=> Called macro_method within FooClass

FooClass.new.instance_method
#=> Called instance_method within #<FooClass:0x192abe0>

module FooModule
  macro_method
end
#=> Called macro_method within FooModule

Это всего лишь 10 строк кода против ваших 16, и ровно 0 из этих 10 строк являются метапрограммированием или хуками или чем-то еще сложный.

Единственное различие между вашим кодом и моим состоит в том, что в вашем коде миксины отображаются в иерархии наследования, поэтому отладить его немного проще, потому что вы на самом деле видите , что что-то было добавлено в Объект .Но это легко исправить:

module ObjectExtensions
  def instance_method
    puts "Called instance_method within #{inspect}"
  end
end

class Object
  include ObjectExtensions
end

module ModuleExtensions
  def macro_method
    puts "Called macro_method within #{inspect}"
  end
end

class Module
  include ModuleExtensions
end

Теперь я привязан к вашему коду на 16 строках, но я бы сказал, что мой код проще, чем ваш, особенно если учесть, что ваш код не работает, и ни вы, ни я, ни почти 190000 пользователей StackOverflow не могут выяснить почему.

22
ответ дан 8 December 2019 в 05:53
поделиться