Accepting IO/#to_io and Strings/#to_s and StringIO

Hi,

I often have a method which takes any kind of IO or something that
explicitly converts to a String.

def test(foo)
  io = IO.try_convert(foo) || StringIO.new(String(foo))
  ...
end

Unfortunately this does not work with StringIO. StringIO does not
respond to #to_io which is used by #try_convert, and StringIO.new does
not accept StringIOs. #is_a?(StringIO) is false as well.

This messes everything up and the method from above becomes something
like this:

def test(foo)
  io = IO.try_convert(foo) ||
       foo.is_a?(StringIO) ? foo : StringIO.new(String(foo))
  ...
end

Is there another approach where StringIO behaves more like IO? What is
the reason that StringIO does not respond to #to_io?

Cheers,
Christian

···

--
ifu Hamburg - material flows and software
"We enable sustainable production."

ifu Hamburg GmbH
Max-Brauer-Allee 50 - 22765 Hamburg - Germany
fon: +49 40 480009-0 - fax: +49 40 480009-22 - email: info@ifu.com

Managing Director: Jan Hedemann - Commercial Register: Hamburg, HRB 52629
www.ifu.com - www.umberto.de - www.e-sankey.com

Hi,

try_convert is better than kind_of? but duck typing is even better.
Don't abuse try_convert. In your example, I recommend:

  def test(foo)
    tmp = String.try_convert(foo)
    if tmp
      foo = StringIO.new(tmp)
    end
    ...
  end
  test("a")
  test(STDIN)

Consider try_convert as last resort of mixed typed arguments.

              matz.

···

In message "Re: Accepting IO/#to_io and Strings/#to_s and StringIO" on Tue, 01 Dec 2015 15:02:23 +0100, Christian Haase <c.haase@ifu.com> writes:

Hi,

I often have a method which takes any kind of IO or something that
explicitly converts to a String.

def test(foo)
io = IO.try_convert(foo) || StringIO.new(String(foo))
...
end

Unfortunately this does not work with StringIO. StringIO does not
respond to #to_io which is used by #try_convert, and StringIO.new does
not accept StringIOs. #is_a?(StringIO) is false as well.

This messes everything up and the method from above becomes something
like this:

def test(foo)
io = IO.try_convert(foo) ||
      foo.is_a?(StringIO) ? foo : StringIO.new(String(foo))
...
end

Is there another approach where StringIO behaves more like IO? What is
the reason that StringIO does not respond to #to_io?

Cheers,
Christian

--
ifu Hamburg - material flows and software
"We enable sustainable production."

ifu Hamburg GmbH
Max-Brauer-Allee 50 - 22765 Hamburg - Germany
fon: +49 40 480009-0 - fax: +49 40 480009-22 - email: info@ifu.com

Managing Director: Jan Hedemann - Commercial Register: Hamburg, HRB 52629
www.ifu.com - www.umberto.de - www.e-sankey.com

Hi,

I see your point. I like the way how test(5).rewind raises a clear
NoMethodError.

[
  File.open('/etc/papersize'),
  "Test",
  5,
  StringIO.new('Hi'),
].collect { |x| test(x).read rescue $! }

=> ["a4\n", "Test", #<NoMethodError: undefined method `read' for
5:Fixnum>, "Hi"]

In my specific case I want to be very forgiving with the argument and
explicitly convert the argument to something IO-alike; test(5).rewind
should not raise.

At first glance your example accepts #to_io and #to_str. I need to
accept #to_io and #to_s (as StringIO).

IO.responds to both, #to_io and #to_s, so I need to do the #to_io-check
first. StringIO does not respond to #to_io, so it hits #to_s:

def test(foo)
  tmp = IO.try_convert(foo) # or to_io
  tmp ||= StringIO.new(foo.to_s)
  tmp
end

=> ["a4\n", "Test", "5", "#<StringIO:0x00000003429ca0>"]

So I added the typecheck again. The new example with StringIO working as
expected:

def test(foo)
  tmp = IO.try_convert(foo) # or to_io
  tmp ||= foo if foo.is_a? StringIO
  tmp ||= StringIO.new(foo.to_s)
  tmp
end

# => ["a4\n", "Test", "5", "Hi"]

I guess I am in the situation to either typecheck for StringIO or
typecheck for Numeric, String, ... to check if explicit conversion is
safe (safe != "#<...>").

Any thoughts about this?

Cheers,
Christian

···

--
ifu Hamburg - material flows and software
"We enable sustainable production."

ifu Hamburg GmbH
Max-Brauer-Allee 50 - 22765 Hamburg - Germany
fon: +49 40 480009-0 - fax: +49 40 480009-22 - email: info@ifu.com

Managing Director: Jan Hedemann - Commercial Register: Hamburg, HRB 52629
www.ifu.com - www.umberto.de - www.e-sankey.com

Hi,

I don't see your point. What do you want test(5).rewind to behave?
It doesn't seem reasonable for me. If you want test() to accept
strings or IOs, my code should be OK. If you want to do something
very specific to your need, you need to do something unusual.
Dispatching on class using kind_of? may work for you (although I don't
recommend).

There's no room to change to_io to return StringIO, in any
case. Because try_convert and to_xxx exist for compromise between Ruby
duck typing world and C typed world. to_xxx should return an object of
the class exactly specified by the name (IO this case).

              matz.

···

In message "Re: Accepting IO/#to_io and Strings/#to_s and StringIO" on Tue, 01 Dec 2015 20:10:03 +0100, Christian Haase <c.haase@ifu.com> writes:

Hi,

I see your point. I like the way how test(5).rewind raises a clear
NoMethodError.

[
File.open('/etc/papersize'),
"Test",
5,
StringIO.new('Hi'),
].collect { |x| test(x).read rescue $! }

=> ["a4\n", "Test", #<NoMethodError: undefined method `read' for
5:Fixnum>, "Hi"]

In my specific case I want to be very forgiving with the argument and
explicitly convert the argument to something IO-alike; test(5).rewind
should not raise.

At first glance your example accepts #to_io and #to_str. I need to
accept #to_io and #to_s (as StringIO).

IO.responds to both, #to_io and #to_s, so I need to do the #to_io-check
first. StringIO does not respond to #to_io, so it hits #to_s:

def test(foo)
tmp = IO.try_convert(foo) # or to_io
tmp ||= StringIO.new(foo.to_s)
tmp
end

=> ["a4\n", "Test", "5", "#<StringIO:0x00000003429ca0>"]

So I added the typecheck again. The new example with StringIO working as
expected:

def test(foo)
tmp = IO.try_convert(foo) # or to_io
tmp ||= foo if foo.is_a? StringIO
tmp ||= StringIO.new(foo.to_s)
tmp
end

# => ["a4\n", "Test", "5", "Hi"]

I guess I am in the situation to either typecheck for StringIO or
typecheck for Numeric, String, ... to check if explicit conversion is
safe (safe != "#<...>").

Any thoughts about this?

Cheers,
Christian

--
ifu Hamburg - material flows and software
"We enable sustainable production."

ifu Hamburg GmbH
Max-Brauer-Allee 50 - 22765 Hamburg - Germany
fon: +49 40 480009-0 - fax: +49 40 480009-22 - email: info@ifu.com

Managing Director: Jan Hedemann - Commercial Register: Hamburg, HRB 52629
www.ifu.com - www.umberto.de - www.e-sankey.com