Как выбрать версию модуля для динамического включения в Ruby? - PullRequest
10 голосов
/ 29 июля 2010

Я пишу небольшое приложение командной строки Ruby, которое использует fileutils из стандартной библиотеки для файловых операций.В зависимости от того, как пользователь вызывает приложение, я хочу указать либо FileUtils, FileUtils::DryRun, либо FileUtils::Verbose.

Поскольку include является приватным, я не могу применить логику квыберите в методе initialize объекта.(Это была моя первая мысль, с тех пор я мог просто передать информацию о выборе пользователя в качестве параметра new.) Я предложил два варианта, которые, кажется, работают, но я тоже не доволен:

  1. Задайте глобальную переменную в пространстве имен приложения на основе выбора пользователя, а затем выполните условное включение в класс:

    class Worker
      case App::OPTION
      when "dry-run"
        include FileUtils::DryRun
        etc.
    
  2. Создание подклассов, где единственная разница в том, какую версию FileUtils они включают.Выберите подходящий, в зависимости от выбора пользователя.

    class Worker
      include FileUtils
      # shared Worker methods go here
    end
    class Worker::DryRun < Worker
      include FileUtils::DryRun
    end
    class Worker::Verbose < Worker
      include FileUtils::Verbose
    end
    

Первый метод выглядит DRY-er, но я надеюсь, что есть что-то более простое, о чем я не думализ.

Ответы [ 3 ]

8 голосов
/ 29 июля 2010

Так что, если это личное?

class Worker
  def initialize(verbose=false)
    if verbose
      (class <<self; include FileUtils::Verbose; end)
    else
      (class <<self; include FileUtils; end)
    end
    touch "test"
  end
end

Сюда входит FileUtils::something, в частности, метакласс Worker, а не основной Worker класс. Разные работники могут использовать разные FileUtils таким образом.

0 голосов
/ 15 июня 2015

Условно включение модуля через методы отправки работает у меня как в тестируемом примере ниже:

class Artefact
  include HPALMGenericApi
  # the initializer just sets the server name we will be using ans also the 'transport' method : Rest or OTA (set in the opt parameter)
  def initialize server, opt = {}  
    # conditionally include the Rest or OTA module
    self.class.send(:include, HPALMApiRest) if (opt.empty? || (opt && opt[:using] opt[:using] == :Rest)) 
    self.class.send(:include, HPALMApiOTA) if (opt && opt[:using] opt[:using] == :OTA)    
    # ... rest of initialization code  
  end
end
0 голосов
/ 04 марта 2013

Если вы хотите избежать «переключения» и ввести модуль,

def initialize(injected_module)
    class << self
        include injected_module
    end
end

синтаксис не будет работать (переменная injected_module находится вне области видимости). Вы могли бы использовать трюк self.class.send, но расширение экземпляра объекта кажется мне более разумным не только потому, что оно короче:

def initialize(injected_module = MyDefaultModule)
    extend injected_module
end

, но также сводит к минимуму побочные эффекты - общее и легко изменяемое состояние класса, которое может привести к неожиданному поведению в более крупном проекте. В Ruby нет, так сказать, настоящей «приватности», но некоторые методы помечаются как частные не без причины.

...