Рассмотрите следующее расширение (шаблон популяризированный несколькими плагинами направляющих за эти годы):
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?
Рассмотрим следующее расширение (шаблон, популяризированный несколькими плагинами 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 не могут выяснить почему.