Хорошо, вот рабочий код.Обратите внимание, что вам даже не нужно трогать целевой класс!:)
class Klass
def self.say
puts 'class'
end
end
module FooModule
def self.included base
base.instance_eval do
def say
puts "module"
end
end
end
end
Klass.send(:include, FooModule)
Klass.say
Объяснение
Теперь классический способ смешивания в методах класса таков (и, конечно, это не решает проблему).
module FooModule
def self.included base
base.extend ClassMethods
end
module ClassMethods
def bar
puts "module"
end
end
end
class Klass
include FooModule
def self.bar
puts 'class'
end
end
Klass.bar #=> class
Когда модули включены или расширены в класс, его методы размещаются прямо над методами этого класса в цепочке наследования.Это означает, что если бы мы вызвали super
в этом методе класса, он напечатал бы «модуль».Но мы не хотим касаться оригинального определения класса, мы хотим изменить его извне.
Итак, можем ли мы что-то сделать?
Хорошо для нас, у ruby есть концепция «открытые уроки» .Это означает, что мы можем изменить практически все в приложении, даже некоторые сторонние библиотеки.Каждый класс может быть «открыт» и новые методы могут быть добавлены к нему, или старые методы могут быть переопределены.Давайте посмотрим, как это работает.
class Klass
def self.bar
puts 'class'
end
end
class Klass
def self.bar
puts 'class 2'
end
end
Klass.bar #=> class 2
Определение второго класса не переписывает предыдущее, оно открывает и изменяет его.В этом случае случилось, чтобы определить метод с тем же именем.Это привело к тому, что старый метод был заменен новым.Это работает с любыми классами, даже с базовыми библиотеками.
puts [1, 2, 3].to_s #=> [1, 2, 3]
class Array
def to_s
"an array: #{join ', '}"
end
end
puts [1, 2, 3].to_s #=> an array: 1, 2, 3
Или тот же код можно переписать как
puts [1, 2, 3].to_s #=> [1, 2, 3]
Array.class_eval do
def to_s
"an array: #{join ', '}"
end
end
puts [1, 2, 3].to_s #=> an array: 1, 2, 3
Применение знаний
Давайте начнем с более простыхтакие вещи, как переопределение метода экземпляра.
class Klass
def say
puts 'class'
end
end
module FooModule
def self.included base
base.class_eval do
def say
puts "module"
end
end
end
end
Klass.send(:include, FooModule)
Klass.new.say #=> module
Модули имеют специальный обратный вызов, который вызывается каждый раз, когда модуль включается в класс.Мы можем использовать это для вызова class_eval для этого класса и переопределения метода.
Замена метода класса выполняется аналогичным образом.
class Klass
def self.say
puts 'class'
end
end
module FooModule
def self.included base
base.instance_eval do
def say
puts "module"
end
end
end
end
Klass.send(:include, FooModule)
Klass.say #=> module
Единственное отличие здесь заключается в том, что мы вызываем instance_evalвместо class_eval.Это может быть очень запутанной частью.Короче говоря, class_eval создает методы экземпляра, а instance_eval создает методы класса.
Это взято из моего сообщения в блоге .