Posted on 11/6/2023 in ruby, httparty
Upload a file with HTTParty without using a temporary file
When you need to upload a file to HTTP POST endpoint using form encoding and you want to use HTTParty to upload data that you have as a string you have two options:
- Write to a temp file and upload that
- Write the data directly, bypassing the need for a temp file
While you could write the string to a temp file and upload the result it feels a lot cleaner to write the string contents directly.
This is a little more involved and it goes like this:
require 'httparty'
module YourApiWrapper
class Base
include HTTParty
base_uri 'https://examples.com:/api/v1'
headers({
accept: 'application/json',
authorization: "Bearer: #{ENV['YOUR_AUTH_TOKEN']}"
})
end
class File < Base
def self.attach(some_id:, file_name:, file_contents:)
file_to_upload = StringIO.new file_contents
def file_to_upload.path=(path)
@path = path
end
def file_to_upload.path
@path
end
file_to_upload.path = file_name
file_to_upload.rewind
post(
'/files',
multipart: true,
body: {
file: file_to_upload,
some_id: some_id
}
)
end
end
endrequire 'httparty'
module YourApiWrapper
class Base
include HTTParty
base_uri 'https://examples.com:/api/v1'
headers({
accept: 'application/json',
authorization: "Bearer: #{ENV['YOUR_AUTH_TOKEN']}"
})
end
class File < Base
def self.attach(some_id:, file_name:, file_contents:)
file_to_upload = StringIO.new file_contents
def file_to_upload.path=(path)
@path = path
end
def file_to_upload.path
@path
end
file_to_upload.path = file_name
file_to_upload.rewind
post(
'/files',
multipart: true,
body: {
file: file_to_upload,
some_id: some_id
}
)
end
end
endLet's break this down, shall we?
The base class contains the basic config for HTTParty, nothing fancy there.
The crucial part is this:
file_to_upload = StringIO.new file_contentsfile_to_upload = StringIO.new file_contentsIt creates a StringIO Object, which behaves just like a File, but is created on the fly from data. Yet HTTParty refuses to use it as it lacks one component, and that is a path, or filename if you will.
Ruby's dynamic nature lets us monkeypatch that right into the existing object. As we want it to be dynamic, we need a setter and a getter:
def file_to_upload.path=(path)
@path = path
end
def file_to_upload.path
@path
enddef file_to_upload.path=(path)
@path = path
end
def file_to_upload.path
@path
endAnd as this is a file, and we're in the 21st century, we must take care to rewind it before we can use it:
file_to_upload.path = file_name
file_to_upload.rewindfile_to_upload.path = file_name
file_to_upload.rewindAll done, now HTTParty will upload our file:
post(
'/files',
multipart: true,
body: {
file: file_to_upload,
some_id: some_id
}
)post(
'/files',
multipart: true,
body: {
file: file_to_upload,
some_id: some_id
}
)Clicking this button loads third-party content from utteranc.es and github.com