Reputation: 19705
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:
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.
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.
Upvotes: 12
Views: 4169
Reputation: 152
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
Upvotes: 1
Reputation: 313
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.
Upvotes: 0
Reputation: 18861
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.
Upvotes: 8