I'm generating PDFs with a background image uploaded with Carrierwave. Carrierwave pseudo IO object is either CarrierWave::SanitizedFile (in development) or CarrierWave::Storage::Fog::File (in production). Both of them have read method, but they don't support rewind (and they can be read multiple times, it's always from the beginning). I checked Prawn code and discovered that it could be easily made more universal (possibly support other objects acting IO-like without rewind).
def verify_and_open_image(io_or_path)
# File or IO
if io_or_path.respond_to?(:rewind)
io = io_or_path
# Rewind if the object we're passed is an IO, so that multiple embeds of
# the same IO object will work
io.rewind
# read the file as binary so the size is calculated correctly
# guard binmode because some objects acting io-like don't implement it
io.binmode if io.respond_to?(:binmode)
return io
end
# String or Pathname
io_or_path = Pathname.new(io_or_path)
fail ArgumentError, "#{io_or_path} not found" unless io_or_path.file?
io = io_or_path.open('rb')
io
end
The only method required after verify_and_open_image is read, but verify_and_open_image checks for rewind.
io = verify_and_open_image(file)
image_content = io.read
How about checking for the read method in verify_and_open_image and making io.rewind optional, as io.binmode?
def verify_and_open_image(io_or_path)
if io_or_path.respond_to?(:read)
io = io_or_path
# Rewind if the object we're passed is an IO, so that multiple embeds of
# the same IO object will work
# Guard rewind because some objects acting io-like don't implement it
io.rewind if io.respond_to?(:rewind)
# Read the file as binary so the size is calculated correctly
# Guard binmode because some objects acting io-like don't implement it
io.binmode if io.respond_to?(:binmode)
io
else
path = Pathname.new(io_or_path)
fail ArgumentError, "#{path} not found" unless path.file?
io = path.open('rb')
io
end
end
I was able to work around the issue in my code like this
require "open-uri"
def prawn_image
if Rails.env.production?
open(model.uploader_url)
else
model.uploader.current_path
end
end
With the rewind guard it could look like this
def prawn_image
model.uploader.file
end
What do you think?