I'm trying to build an API backend for file uploads. I want to be able to upload files with a POST request that has a Base64-encoded string of the file. The server should decode the string, and save the file using CarrierWave. Here's what I have so far:
photo.rb:
class Photo
include Mongoid::Document
include Mongoid::Timestamps
mount_uploader :image_file, ImageUploader
end
image_uploader.rb:
class ImageUploader < CarrierWave::Uploader::Base
storage :file
def store_dir
"uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
end
end
Rails console:
(summary)
ruby-1.8.7-p334 :001 > img = File.open("../image.png") {|i| i.read}
=> "\377���JFIF\000\001\002\001\000H\000H\000\000\377�Photoshop 3.0\0008BIM\003...
ruby-1.8.7-p334 :003 > encoded_img = Base64.encode64 img
=> 3af8A\nmLpplt5U8q+a7G2...
ruby-1.8.7-p334 :005 > p = Photo.new
=> #<Photo _id: 4e21b9a31d41c817b9000001, created_at: nil, updated_at: nil, _type: nil, user_id: nil, image_file_filename: nil>
ruby-1.8.7-p334 :006 > p.user_id = 1
=> 1
ruby-1.8.7-p334 :007 > p.image_file = Base64.decode64 encoded_img
\255��=\254\200�7u\226���\230�-zh�wT\253%����\036ʉs\232Is�M\215��˿6\247\256\177...
ruby-1.8.7-p334 :008 > p.save
=> true
ruby-1.8.7-p334 :009 > p.image_file.url
=> nil
full
The problem appears to be related to the process of converting a Base64-decoded string to a file. CarrierWave seems to expect a File object, and instead I'm giving it a String. So how do I convert that String to a File object. I'd like this conversion not to save anything to the file system, simply create the object and let CarrierWave do the rest.
CarrierWave also accepts a StringIO, but it expects a original_filename
method, since it needs it for figuring out the file name and doing the extension check. How you do it changed between Rails 2 and 3, here's both methods:
Rails 2
io = StringIO.new(Base64.decode64(encoded_img))
io.original_filename = "foobar.png"
p.image_file = io
p.save
In Rails 3, you need to make a new class and then manually add original_filename
back
class FilelessIO < StringIO
attr_accessor :original_filename
end
io = FilelessIO.new(Base64.decode64(encoded_img))
io.original_filename = "foobar.png"
p.image_file = io
p.save
You don't have to monkeypatch StringIO or put any of this in your model. You can override the cache!() method in your uploader definition. Or you can take it a step further and make a module for yourself to include. My file is a serialized string coming from a json document. The object passed in looks like this { :filename => 'something.jpg', :filedata => base64 string }.
Here is my module:
module CarrierWave
module Uploader
module BackboneLink
def cache!(new_file=sanitized_file)
#if new_file isn't what we expect just jump to super
if new_file.kind_of? Hash and new_file.has_key? :filedata
#this is from a browser, so it has all that 'data:..' junk to cut off.
content_type, encoding, string = new_file[:filedata].split(/[:;,]/)[1..3]
sanitized = CarrierWave::SanitizedFile.new(
:tempfile => StringIO.new(Base64.decode64(string)),
:filename => new_file[:filename],
:content_type => content_type
)
super sanitized
else
super
end
end
end
end
end
And then I can include it in an uploader.
uploaders/some_uploader.rb:
class SomeUploader < CarrierWave::Uploader::Base
include CarrierWave::Uploader::BackboneLink
class AppSpecificStringIO < StringIO
attr_accessor :filepath
def initialize(*args)
super(*args[1..-1])
@filepath = args[0]
end
def original_filename
File.basename(filepath)
end
end
also see the carrierwave wiki https://github.com/carrierwaveuploader/carrierwave/wiki/How-to%3A-Upload-from-a-string-in-Rails-3