Какой самый сухой способ расширения / исправления библиотеки в ruby?

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

Конкретная задача, которую я пытаюсь выполнить, это расширение рубина. Net::FTP модуль для поддержки некоторых не соответствующих стандартам серверов. Такое расширение должно быть полностью отделено от совместимой со стандартами библиотеки IMHO.

Я думал, что требовать дополнительного файла было бы неплохо, так как это даже не создавало бы необходимость какого-то переключения в исходном коде. Так что дополнительный require 'net/ftp/forgiving' сделало бы оригинальную библиотеку немного более щадящей в отношении наших менее талантливых собратьев по FTP-серверу.

Соответствующий файл может затем использовать открытый класс ruby ​​и архитектуру модуля для исправления класса FTP. Для исправления примера причудливого поведения, связанного выше, мне нужно будет исправить Net::FTP#mkdir, который будет выглядеть так:

#content of net/ftp/forgiving
require 'net/ftp'

module Net
  class FTP

    # mkdir that will accept a '250 Directory created' as a valid response
    def mkdir(dirname)
      begin
        original_mkdir(dirname)
      rescue FTPReplyError => e
        raise unless e.message.start_with? '250 Directory created'
        return ""
      end
    end

  end
end

Однако для этого потребуется как-то кешировать оригинал Net::FTP#mkdir как Net::FTP#original_mkdir держать код СУХОЙ. Это возможно? Есть ли у вас какие-либо дальнейшие предложения о том, как улучшить этот метод исправления / расширения? Или, может быть, даже совершенно разные подходы?

1 ответ

Решение

Это называется "monkeypatching" и это именно тот случай использования, который alias_method был сделан для:

alias_method :original_mkdir, :mkdir
def mkdir(dirname)
  begin
    original_mkdir(dirname)
  rescue FTPReplyError => e
    raise unless e.message.start_with? '250 Directory created'
    return ""
  end
end

Хотя это часто встречающаяся "идиома" в Ruby, это нарушит существующий код (возможно, даже код внутри Net) который опирается на mkdir поднимая исключение в этом случае. Вы не можете ограничить эти изменения файлами, которые require 'net/ftp/forgiving' только. Таким образом, было бы намного чище создать подкласс, а не открывать исходный класс:

module Net
  class ForgivingFTP < FTP
    # mkdir that will accept a '250 Directory created' as a valid response
    def mkdir(dirname)
      begin
        super(dirname)
      rescue FTPReplyError => e
        raise unless e.message.start_with? '250 Directory created'
        return ""
      end
    end
  end
end

Или, что еще лучше, поместите его в собственное пространство имен! Хорошее эмпирическое правило:

Подкласс, когда это возможно, Monkeypatch, когда это необходимо.

(Спасибо @tadman за это). В этом случае это не кажется необходимым.

ОБНОВЛЕНИЕ: После вашего комментария, если вы хотите расширить только конкретный экземпляр Net::FTP класс, вы можете расширить их одноэлементные классы:

obj = Net::FTP.new
class << obj
  alias_method :original_mkdir, :mkdir
  def mkdir(dirname)
    #...
    original_mkdir(dirname)
    #...
  end
end
Другие вопросы по тегам