Проблема

Допустим, есть класс:

class Person
def do_stuff
some_stuff
another_stuff
and_another_stuff
end
end

И мы не имеем прямого доступа к его коду, потому что он находится в геме.

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

Решение №1

Для решения данной задачи нужно использовать декоратор. Но как будет выглядеть переопределение метода? Кто-то может сразу написать что-то вроде:

Person.class_eval
def do_stuff
super
my_stuff
end
end

Однако, это не будет работать, потому что super вызывает метод суперкласса или включенного модуля, которого у нас попросту нет (на самом деле есть, но это уже другая история), а если бы и был, то это явно не то, что нам нужно.

Решение №2

Другим решением будет, если мы откроем код гема и просто скопируем метод, добавив туда нашу заветную строчку:

Person.class_eval
def do_stuff
some_stuff
another_stuff
and_another_stuff

my_stuff
end
end

Это уже будет работать, но согласитесь, что такое решение “попахивает”. Более того, если в новом обновлении гема этот метод постигнут какие-нибудь изменения (например, туда добавится вызов important_stuff), мы их просто не получим, потому что наш метод копирует поведение предыдущей версии. В общем, не самое удачное решение.

Правильное решение

Сразу приведу код:

Person.class_eval
alias original_do_stuff do_stuff # or use #alias_method if you prefer it

def do_stuff
original_do_stuff

my_stuff
end
end

Возможно, вы посчитаете, что такой код должен войти в бесконечную рекурсию, ведь мы вызываем do_stuff в do_stuff, но нет, original_do_stuff выполняет именно изначальный метод (до переопределения).

Если посмотреть код самого Ruby, то можно обнаружить, что при поиске метода в классе, возвращается указатель на него. Т.е. при использовании alias в таблице методов создается новый метод, со ссылкой на определение метода, который указан вторым аргументом. При перезаписывании метода мы создаем новое определение и заменяем адрес в таблице методов.

Объяснение примерное и частично интуитивное, т.к. я не стал слишком глубоко погружаться в сишный код. Да и зачем, собственно?

Заключение

У меня однажды возникла такая задача на рабочем проекте. Возможно, необходимость использования подобных “хаков” является следствием плохо продуманной архитектуры проекта. Но задача есть задача, и она выполнена.

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

Удачи!