I have a set of methods (related to logging), where the “output device”
for the logging can be either
A String (representing a file name),
An object of type File
An object of type StringIO (logging to a string)
In all places except one, I don’t need to worry about the actual type of
my device object. The exception is, when, at a given point, I need to
“delete the outputfile (if it exists)”.
My first approach was:
File.unlink(device) if File.file?(device)
I hoped that, since StringIO is derived from IO, File::file? would
recognize that this is not a file (since many class methods inside File
can work with arbitrary IO objects).
This is not the case here: File.file? is not defined for a StringIO
object.
I’m using now this workaround:
File.unlink(device) if device.class.name != 'StringIO' &&
File.file?(device)
I don’t like it, though. Querying the class name always looks like a
hack.
Use a symbol representing the required method. I don’t know what method
it is that is being called against your object, but maybe use something
like device.respond_to?(:unlink) and make your decision from there.
It doesn’t have to be that method, just one which is unique to the
relevant class. Preferably relevant to whatever happens inside the
File.unlink method.
If you want to actually compare the class but don’t like the
“.class.name” syntax, you could try StringIO === device
I don’t know what method
it is that is being called against your object, but maybe use something
like device.respond_to?(:unlink) and make your decision from there.
No method is called against my object. This is why respond_to? does
not work. As you can see from the code:
File.unlink?(device)
The receiver of :unlink is the class File (which, of course, responds
to unlink), not my variable.
If I would use
device.respond_to?(:unlink)
this would ALWAYS be false, even if device is of type String or File,
because there is no instance method :unlink for these classes.
It doesn’t have to be that method, just one which is unique to the
relevant class.
Ah, I get the point.
I was considering this, but from a design viewpoint, this looks even
worse to me than querying the class type, because I become dependent on
the implementation:
If I just select some arbitrary (documented) method f from, say,
StringIO, which is neither in File nor in String, to distinguish these
cases, and in a later version of Ruby, this method would be introduced
in File and/or String as well, my code would break.
If I look at the implementation of File::unlink, and find that some
method g is invoked on the argument, which happens to be not present in
StringIO, I could use respond_to? on this method, and it would work now,
but this again depends then on the implementation, which should be
regarded as a black box too.
So, in both cases, it feels like a hack to me, and much harder to
understand than the - admittedly ugly - querying of the object class.
BTW, your suggestion to write “device == StringIO” doesn’t work either,
because ‘device’ is of class StringIO, it is not “the class” StringIO.
We can easily see it in irb:
There is also the method #is_a?, but this checks superclasses as well,
which may change in a future version of ruby. With #instance_of?, your
code will only break if the class gets renamed, but then you would have
to change your code anyway, as File.unlink etc. would not work
anymore.
is_a? won’t change, I just meant that if ever, say StringIO would become
a subclass of File, it might stop working. Not too likely, however, you
are rigt, so go with is_a?.