How can I choose which version of a module to incl

2019-04-05 16:50发布

I'm writing a small Ruby command-line application that uses fileutils from the standard library for file operations. Depending on how the user invokes the application, I will want to include either FileUtils, FileUtils::DryRun or FileUtils::Verbose.

Since include is private, though, I can't put the logic to choose into the object's initialize method. (That was my first thought, since then I could just pass the information about the user's choice as a parameter to new.) I've come up with two options that seem to work, but I'm not happy with either:

  1. Set a global variable in the app's namespace based on the user's choice, and then do a conditional include in the class:

    class Worker
      case App::OPTION
      when "dry-run"
        include FileUtils::DryRun
        etc.
    
  2. Create sub-classes, where the only difference is which version of FileUtils they include. Choose the appropriate one, depending on the user's choice.

    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
    

The first method seems DRY-er, but I'm hoping that there's something more straightforward that I haven't thought of.

3条回答
该账号已被封号
2楼-- · 2019-04-05 17:19

If you would like to avoid the "switch" and inject the module, the

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

syntax won't work (the injected_module variable is out of scope). You could use the self.class.send trick, but per object instance extending seems more reasonable to me, not only because it is shorter to write:

def initialize(injected_module = MyDefaultModule)
    extend injected_module
end

but also it minimizes the side effects - the shared and easily changable state of the class, which can result in an unexpected behavior in a larger project. In Ruby the is no real "privacy" so to say, but some methods are marked private not without a reason.

查看更多
闹够了就滚
3楼-- · 2019-04-05 17:35

So what if it's private?

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

This includes FileUtils::something in particular's Worker's metaclass - not in the main Worker class. Different workers can use different FileUtils this way.

查看更多
我命由我不由天
4楼-- · 2019-04-05 17:41

Conditionally including the module through the send methods works for me as in the below tested example:

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
查看更多
登录 后发表回答