Trying to avoid querying the object class

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.

Can someone suggest a better way to do it?

Have you tried the “respond_to?” method?

Joel P. wrote in post #1178902:

Have you tried the “respond_to?” method?

But what argument should I give to “respond_to?”? Look at my example:
The variable ‘device’ is used as argument for class member functions
only.

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

Joel P. wrote in post #1178913:

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.

Joel P. wrote in post #1178930:

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:

irb(main):002:0> require ‘stringio’
=> true
irb(main):003:0> device=StringIO.new
=> #StringIO:0x00000600267798
irb(main):004:0> device == StringIO
=> false

Personally I prefer the following syntax, as it looks the least hacky:

x.instance_of?(String)
x.instance_of?(StringIO)
x.instance_of?(File)

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.

Dansei Yuuki wrote in post #1178934:

Personally I prefer the following syntax, as it looks the least hacky:

x.instance_of?(String)
x.instance_of?(StringIO)
x.instance_of?(File)

Good point. I forgot about this.

There is also the method #is_a?, but this checks superclasses as well,
which may change in a future version of ruby.

Why do you think #is_a? might change? It is pretty fundamental, and in
my case,

device.is_a?(String) || device.is_a?(File)

would even make more sense, because it would work also for derived
classes of String and File.

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?.

I don’t think StringIO can ever become a subclass of File, because many
File operations don’t make sense with StringIO objects.

A more reasonable scenario would be that File::unlink is modified to
accept any IO object as argument, but this would not break my code.

So I think I’m safe here.