Post image from Rails - ruby-on-rails

I've got a Base64 encoded image coming in to my application. I want to re-post that image somewhere else, but it's setting the content-type to multipart/form-data at the destination. How do I upload this image?
file_name = permitted_params[:file_name]
file_contents = permitted_params[:file_contents]
file = Tempfile.new( file_name )
file.binmode
file.write( Base64.decode64( file_contents ) )
file.rewind()
raw_response = RestClient.put(
url,
{ 'upload' => file, :content_type => 'image/jpeg' },
:headers => {:content_type => 'image/jpeg'}
)
UPDATE (SOLVED)
I needed to use RestClient because I needed to pass it through to another server (hence the 'url' in the PUT).
My problem was in decoding the image I wasn't stripping out the
data:image/jpeg;base64,
then with this code:
raw_response = RestClient.put(url,
file_binary,
{:content_type => imageContentType})
I was able to get it to put the image and set the correct content-type. The answer below did help though, because I tried it to make sure the image was being decoded properly and it wasn't.

It is quite simple to do. First, you need to decode base64 encoded file. You will get binary file representation. Next use send_data from ActionController to send binary data. Also I have set a filename so it will be delivered to the user.
require 'base64'
class SomeController < ApplicationController
def some_action
file_name = permitted_params[:file_name]
file_base64_contents = permitted_params[:file_contents]
file_binary_contents = Base64.decode64(file_base64_contents)
# https://apidock.com/rails/ActionController/Streaming/send_data
send_data file_binary_contents, filename: file_name
end
end
I'll suggest you to update this implementation with error handling to improve security of your app. One more thing, don't use RestClient. Why do you need it here? Rails gives you all needed things for HTTP communication from controller.
If you have any questions about this please ask. Good luck.

Related

Send multiple files to a webservice

I'm currently trying to send multiple files to a webservice (and to proceed, depending on the response afterwards, but that's not where I am at yet).
The following code sends one file:
def show
...
conn = Faraday.new(:url => 'webservice.abc' ) do |faraday|
faraday.request :multipart
faraday.adapter :net_http
end
payload = { :files => Faraday::UploadIO.new("#{Rails.root}/fileone.xml", 'application/xml') }
conn.post 'http://webservice.abc', payload
#output = response.body
end
And now I'm stuck, trying to find a way to send 2 (or more) files at once, which is necessary as the purpose of the webservice is to compare these. It seems that when I put them into an array, they can't be handled with.
So what I'm looking for is the way to "bundle" the files in order to POST them afterwards (as said before- it works with one file)
TYIA for your time
Thanks Deepak,
gave me a hint into the right direction. Should be :files[i], though- so the payload line from the question reads:
payload = { :files[0] => Faraday::UploadIO.new("#{Rails.root}/fileone.xml", 'application/xml'),
:files[1] => Faraday::UploadIO.new("#{Rails.root}/filetwo.xml", 'application/xml')}

write stream to paperclip

I want to store received email attachment with usage of paperclip. From email I get part.body and I have no idea how to put it to paperclip'ed model. For now I create temporary file and write port.body to it, store this file to paperclip, and delete file. Here is how I do it with temporary file:
l_file = File.open(l_path, "w+b", 0644)
l_file.write(part.body)
oAsset = Asset.new(
:email_id => email.id,
:asset => l_file,
:header => h,
:original_file_name => o,
:hash => h)
oAsset.save
l_file.close
File.delete(l_path)
:asset is my 'has_attached_file' field. Is there a way to omit file creation and to do something like: :asset => part.body in Asset.new ?
This is how I would do it, assuming your using the mail gem to read the email. you'll need the whole email 'part', not just part.body
file = StringIO.new(part.body) #mimic a real upload file
file.class.class_eval { attr_accessor :original_filename, :content_type } #add attr's that paperclip needs
file.original_filename = part.filename #assign filename in way that paperclip likes
file.content_type = part.mime_type # you could set this manually aswell if needed e.g 'application/pdf'
now just use the file object to save to the Paperclip association.
a = Asset.new
a.asset = file
a.save!
Hope this helps.
Barlow's answer is good, but it is effectively monkey-patching the StringIO class. In my case I was working with Mechanize::Download#body_io and I didn't want to possibly pollute the class leading to unexpected bugs popping up far away in the app. So I define the methods on the instances metaclass like so:
original_filename = "whatever.pdf" # Set local variables for the closure below
content_type = "application/pdf"
file = StringIO.new(part.body)
metaclass = class << file; self; end
metaclass.class_eval do
define_method(:original_filename) { original_filename }
define_method(:content_type) { content_type }
end
I like gtd's answer a lot, but it can be simpler.
file = StringIO.new(part.body)
class << file
define_method(:original_filename) { "whatever.pdf" }
define_method(:content_type) { "application/pdf" }
end
There's not really a need to extract the "metaclass" into a local variable, just append some class to the object.
From ruby 1.9, you can use StringIO and define_singleton_method :
def attachment_from_string(string, original_filename, content_type)
StringIO.new(string).tap do |file|
file.define_singleton_method(:original_filename) { original_filename }
file.define_singleton_method(:content_type) { content_type }
end
end
This would have been better as a comment on David-Barlow's answer but I don't have enough reputation points yet...
But, as others mentioned I didn't love the monkey-patching. Instead, I just created a new class that inherited from StringIO, like so:
class TempFile < StringIO
attr_accessor :original_filename, :content_type
end
For posterity, here is the best answer. Put the top part in vendor/paperclip/data_uri_adapter.rb and the bottom part in config/initializers/paperclip.rb.
https://github.com/thoughtbot/paperclip/blob/43eb9a36deb09ce5655028a1061578dbf0268a5d/lib/paperclip/io_adapters/data_uri_adapter.rb
This requires a data URI scheme stream, but these days that seems pretty common. Simply set your paperclip'd variable to a string with the stream data, and the code takes care of the rest.
I used a similar technique to pull down images into paperclip
this should work, but is obvs untested:
io = part.body
def io.original_filename; part.original_file_name || 'unknown-file-name'; end
asset = Asset.new(:email=>email)
asset.asset = io
When we are assigning the IO directly to the paperclip instance, it needs to have a .original_file_name to it, so that's what we're doing in the second line.

Rails + CouchDb binary file upload to Database

Simple-Stored is based on top of CouchPotato for handling CouchDb in rails. While trying to upload files to the couchdb we have tryied using base64, json post and nothing seems to work quite right; we are trying to upload to the _attachments propertie of a document already stored.
Having the model like this :
class Patient
include SimplyStored::Couch
end
and in the controller while receiving the file trough the update action
def update
#patient = Patient.find(params[:id])
if params[:patient][:_attachments]
attachement = params[:patient][:_attachments]
filedata = attachement.tempfile.read
data = Base64.encode64(filedata).gsub(/\n/, '')
type = attachement.content_type
or_name = attachement.original_filename
#patient._attachments = {or_name => {'data' => data, 'content_type' => type}}
#patient.save
return render :json => #patient._attachments
end
end
Now the fun part is that I can see that #patient._acttachments has the file itself and that is what is returning in the render after the .save; but it is not actually saving it on the couchdb database.
Any ideas why is not doing the save or should I try to just push the _attachment to the couchdb database. ? (which by the way always returns a 500 error :( )
the solution it's very simple, based on the couchpotato website, you actually don't need to convert it to base64 here is the example of code working
if params[:patient][:_attachments]
attachement = params[:patient][:_attachments]
data = attachement.tempfile.read
type = attachement.content_type
or_name = attachement.original_filename
params[:patient][:_attachments] = {or_name => {'data' => data, 'content_type' => type}}
end
if #patient.update_attributes(params[:patient]) #blah blah blah
since the values are in the [:patient][:_attachments] params, you just need to pass it as another param tested and working.
Also you need to define your patients model as
property :_attachments
dunno if that is required but I did it.
I know I should not ask for money but since I WORK FOUR YOU its only 100 pesos/hour.. see you at the office
cheers
lols
I donno about the Ruby and couchpotato, but I don't think you need to Base64 your attachment. Just read the binary info and write it to request.
my 2cents. :)

Uploading a file through Paperclip or Carrierwave from a Mail attachment

If I have a mail object, eg:
mail = Mail.new do
from "jim#gmail.com"
to "jane#yahoo.com"
subject "Example"
text_part do
body "Blarg"
end
add_file "/some/file/or/some_such.jpg"
end
If I were to receive the above mail in my application
received_mail = mail.encoded
Message.parse(received_mail)
How would I pass the attachment on to CarrierWave/Paperclip (not fussed about which, I'll use whichever one handles this best)? I've tried a few different methods, but I keep running in to various stumbling blocks - has anyone got a working solution for it?
My current attempt is:
mail.attachments.each do |attachment|
self.attachments << Attachment.new(:file => Tempfile.new(attachment.filename) {|f| f.write(attachment.decoded)})
end
This doesn't appear to work - any tips?
end
I know that when I tried to take mail attachments and use them with paperclip, I also ran into some problems. The problem as I remember it was that paperclip expected certain attributes on the File object passed to it.
I solved it like this:
mail.attachments.each do |attachment|
file = StringIO.new(attachment.decoded)
file.class.class_eval { attr_accessor :original_filename, :content_type }
file.original_filename = attachment.filename
file.content_type = attachment.mime_type
#Then you attach it where you want it
self.attachments << Attachment.new(:file => file)

What is the best way to upload a file to another Rails application?

I 've researched and noticed that ActiveResource lack this functionality. So, what is the current state of the art when doing a file upload?
One problem with Guillermo's approach is that the request has to be nested, like this:
body = { :file => {:uploaded_data => File.open("#{RAILS_ROOT}/public/tmp/" + original_filename), :owner_id => current_user.owner_id }, :api_key => '123123123123123123'}
Of course it is not possible to do a request like this with HttpClient. I tried other gems I found in github (sevenwire-http-client and technoweenie-rest-client) but they have problems with the file being nested. Is it possible to upload a file with a nested request?
The Httpclient gem allows you to do multipart posts like this:
clnt = HTTPClient.new
File.open('/tmp/post_data') do |file|
body = { 'upload' => file, 'user' => 'nahi' }
res = clnt.post(uri, body)
end
You could use this to simply post a file on the local file system to a controller in the other application. If you want to upload data just upload with a form into your app without storing it first, you could probably use the uploaded data from your params immediately in the post body.
You can try something like the following:
#I used the HTTPClient gem as suggested (thanks!)
clnt = HTTPClient.new
# The file to be uploaded is originally on /tmp/ with a filename 'RackMultipart0123456789'.
# I had to rename this file, or the resulting uploaded file will keep that filename.
# Thus, I copied the file to public/tmp and renamed it to its original_filename.(it will be deleted later on)
original_filename = params[:message][:file].original_filename
directory = "#{RAILS_ROOT}/public/temporary"
path = File.join(directory, original_filename)
File.open(path, "w+") { |f| f.write(params[:job_application][:resume].read) }
# I upload the file that is currently on public/tmp and then do the post.
body = { :uploaded_data => File.open("#{RAILS_ROOT}/public/tmp/" + original_filename), :owner_id => current_user.owner_id}
res = clnt.post('http://localhost:3000/files.xml', body)

Resources