Home > Software design >  Why do I sometimes get a Type Error "no implicit conversion of StringIO into String" here?
Why do I sometimes get a Type Error "no implicit conversion of StringIO into String" here?

Time:01-03

I have a line of code in one of my mailers to add an attachment with the surname and DoB of the user. Type Error "no implicit conversion of StringIO into String" sometimes occurs in the File.read method.

attachments["#{@user.last_name.downcase}-#{@user.date_of_birth.strftime('%d%m%y')}.jpeg"] = File.read(URI.parse(@user.profile_picture_url).open)

It works most of the time, but occasionally it just fails with this error. I'm using carrierwave to upload the files to a remote S3 bucket, if that makes a difference.

EDIT:

I've done some digging in the console which has frankly left me more confused! I have two user records, both with profile pictures uploaded via carrierwave to S3. If I just isolate the File.read method and try it using both records one works and one doesn't. Inspecting the URI, they appear to be virtually identical.

However, I have found that carrierwave supports a shortcut to read files which solves the problem. Here's the updated code:

attachments["#{@user.last_name.downcase}-#{@user.date_of_birth.strftime('%d%m%y')}.jpeg"] = @user.profile_picture.read

CodePudding user response:

Anything less than 10kb opens as StringIO instead of File. Couldn't find an actual reference in the docs, so here is the source:

StringMax = 10240
def <<(str)
  @io << str
  @size  = str.length

  # NOTE: once you pass 10kb mark, you get a tempfile.
  #                      v
  if StringIO === @io && StringMax < @size
    require 'tempfile'
    io = Tempfile.new('open-uri')
    io.binmode
    Meta.init io, @io if Meta === @io
    io << @io.string
    @io = io
  end
end

https://github.com/ruby/ruby/blob/v3_2_0/lib/open-uri.rb#L398


Let's test it:

>> require "open-uri"

>> File.write("public/small.txt", "a"*10240)
=> 10240
>> URI.open("http://localhost:3000/small.txt").class
=> StringIO

>> File.write("public/large.txt", "a"*(10240 1))
=> 10241
>> URI.open("http://localhost:3000/large.txt").class
=> Tempfile

You can File.read a Tempfile, but you can't File.read a StringIO.

The fix is to call read on the URI:

>> URI.parse("http://localhost:3000/small.txt").read.class
=> String
>> URI.parse("http://localhost:3000/large.txt").read.class
=> String

# also works
>> URI.open("http://localhost:3000/small.txt").read.class
=> String

https://rubyapi.org/3.2/o/openuri/openread#method-i-read

  • Related