Какой самый сухой способ расширения / исправления библиотеки в ruby? - PullRequest
1 голос
/ 13 февраля 2012

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

Конкретная задача, которую я пытаюсь выполнить, - расширение модуля Net::FTP в ruby ​​для поддержки некоторых не соответствующих стандартам серверов .Такое расширение должно быть полностью отделено от совместимой со стандартами библиотеки 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, чтобы сохранить код DRY.Это возможно?Есть ли у вас какие-либо дальнейшие предложения о том, как улучшить этот метод исправления / расширения?Или, может быть, даже совершенно разные подходы?

1 Ответ

4 голосов
/ 13 февраля 2012

Это называется «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
...