#rails ruby

TILSeptember 18, 2019by Alexander Spitsyn

Rewind Files Before Reading

Suppose you have a form with a file input and you need to forward that file to an external service through your Rails app. Params will look like this:

 { "file"=> #<ActionDispatch::Http::UploadedFile:0x00007fce9a8af140 @tempfile=#<Tempfile:/path/to/the/file.xlsx>, ... > } 

The external service can not be reached for the first time sometimes, so you decided to set some retry count.

 retry_count = 5 while retry_count > 0 begin HTTPClient.new.post(url, params, headers) rescue ExternalServiceUnavailable => e retry_count -= 1 end end 

The code above uses HTTPClient gem for making http requests and it will raise the following error in case there will be at least one retry:

ArgumentError: Illegal size value: #size returns 154139 but cannot read

The thing is that HTTPClient uses IO#read to read the file by chunks for making POST request. The lineno pointer reaches the end of the file after read, and you can not read bytes from the I/O stream anymore:

 params['file'].tempfile.eof? # => true 

To fix that, use IO#rewind – it positions IO to the beginning of input, resetting lineno to zero. Just add checking the file for eof before each request and rewind it if needed:

 retry_count = 5 while retry_count > 0 begin params['file'].tempfile.rewind if params['file'].tempfile.eof? # check eof HTTPClient.new.post(url, params, headers) rescue ExternalServiceUnavailable => e retry_count -= 1 end end