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