18

What is the best way to upload an image from a client to a Rails backend using Carrierwave. Right now our iOS developer is sending in the files as base64, so the requests come in like this:

"image_data"=>"/9j/4AAQSkZJRgABAQAAAQABAAD/4QBYRXhpZgAATU0AKgAAAAgAAgESAAMAAAABAAEAAIdpAAQAAAABAAAAJgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAAAHqADAAQAAAABAAAAHgAAAAD/2wBDAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQH/2wBDAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQH/wAARCAAeAB4DAREAAhEBAxEB/8QAHwAAAQUBAQE....

So, my question is really two questions. Should I tell him to send in a different file format? If base64 is the right way to send these files in, then how do I deal with them in carrierwave?

Jonathan Allard
  • 18,429
  • 11
  • 54
  • 75
botbot
  • 7,299
  • 14
  • 58
  • 96

3 Answers3

29

I think that one solution can be to save the decoded data to file and then assign this file to mounted uploader. And after that get rid of that file.

The other (in-memory) solution can be this one:

# define class that extends IO with methods that are required by carrierwave
class CarrierStringIO < StringIO
  def original_filename
    # the real name does not matter
    "photo.jpeg"
  end

  def content_type
    # this should reflect real content type, but for this example it's ok
    "image/jpeg"
  end
end

# some model with carrierwave uploader
class SomeModel
  # the uploader
  mount_uploader :photo, PhotoUploader

  # this method will be called during standard assignment in your controller
  # (like `update_attributes`)
  def image_data=(data)
    # decode data and create stream on them
    io = CarrierStringIO.new(Base64.decode64(data))

    # this will do the thing (photo is mounted carrierwave uploader)
    self.photo = io
  end

end
Radek Paviensky
  • 8,316
  • 2
  • 30
  • 14
  • i'm curious what the not in-memory solution would be? should i have concerns about performance with this solution? – botbot Feb 15 '13 at 22:53
  • how would image_data=(data) be called? – botbot Feb 16 '13 at 06:28
  • how could I detect the content type? – sites Apr 14 '13 at 01:09
  • I did `def content_type string.match(/data:(.*);/)[1] end` will test – sites Apr 14 '13 at 01:14
  • 7
    I put config.filter_parameters += [:password, :image_data] in config to not to pollute log – sites Apr 14 '13 at 01:16
  • i tried your code but it is creating error jpeg file, i updated the post can u check – santosh Jun 26 '14 at 10:49
  • 1
    I'm using a data URL generated from a canvas. My final image was invalid. You have to make sure the data passed in is only the data, without the "data:image/png," part. – Jonathan Allard Aug 13 '14 at 05:55
  • Hello everyone .. When I try the above I am getting the following error , I am not sure why this is happening , the error is "Cloudinary::CarrierWave::UploadError (Invalid image file): " . Can any one please help me – ratnakar Sep 20 '14 at 10:36
  • How do I get the correct content-type and real image name (would be a problem saving a png file as a jpeg) from that data? And if it comes alongside the data, how do I do this? – Almaron Feb 04 '18 at 19:16
10

You can easily achieve that using Carrierwave-base64 Gem you don't have to handle the data yourself, all you do is add the gem and change your model from

mount_uploader :file, FileUploader

to

mount_base64_uploader :file, FileUploader

and thats it, now you can easily say:

Attachment.create(file: params[:file])
mohamed-ibrahim
  • 10,837
  • 4
  • 39
  • 51
  • This worked for me, but it took me a while to figure out how to add the image data into a user object in my controller. The raw base64 string comes into the controller on the "avatar" parameter and my user model has a carrierwave base64uploader also called "avatar", so it looks like this: user.update_attributes(avatar: "data:image/png;base64," + params[:avatar]) – gravy Jan 28 '20 at 05:07
  • I needed to add data prefix. After adding gem `"data:image/png;base64," + params[:image_file]` it works for me. Thank you @gravy – mahfuz Jan 14 '21 at 05:25
6

Old question but I had to do a similar thing, upload image from base64 string which was passed on via a json request. This is what I ended up doing:

#some_controller.rb
def upload_image
  set_resource
  image = get_resource.decode_base64_image params[:image_string]
  begin
    if image && get_resource.update(avatar: image)
      render json: get_resource
    else
      render json: {success: false, message: "Failed to upload image. Please try after some time."}
    end
  ensure
    image.close
    image.unlink
  end
end

#some_model.rb
def decode_base64_image(encoded_file)
  decoded_file = Base64.decode64(encoded_file)
  file = Tempfile.new(['image','.jpg']) 
  file.binmode
  file.write decoded_file

  return file
end
Mandeep
  • 9,093
  • 2
  • 26
  • 36