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
