Save base64 images with paperclip - ruby-on-rails

I'm currently working on application for saving base64-encoded image as normal png image. I have following code in my controller's create action:
if #campaign.save
unless params[:campaign][:design_attributes][:front_svg].empty?
data = params[:campaign][:design_attributes][:front_svg]
File.open(params[:campaign][:design_attributes][:img_front_file_name], 'wb') do |f|
f.write(ActiveSupport::Base64.decode64(data))
end
f = File.open(params[:campaign][:design_attributes][:img_front_file_name])
#campaign.design.img_front = f
end
end
front_svg params contains base64 data. When I try to call action, I get following error:
no implicit conversion of nil into String
How do I save base64 encoded image using paperclip?

Change f.write like this,
decoded_data = ActiveSupport::Base64.decode64(data)
f.write(StringIO.new(decoded_data))

Related

How to save base64 encoded image with ruby on rails and carrierwave

I receive a base64 encoded image from another server and need to save it to my postgres database. I am using rails 6 and carrierwave. Here is my model:
class Quin < ApplicationRecord
before_create :create_graphic
mount_base64_uploader :new_image, NewImageUploader
def create_graphic
url = "https://my-api-url.com"
url_response = HTTParty.post(url,:body => data)
self.new_image = "data:image/png;base64," + url_response.parsed_response
puts "parsed response is #{url_response.parsed_response.inspect}"
end
end
For some reason, when I save, new_image is just saved as nil. I know I am receiving a response. It's really long but here's a truncated sample from logs:
parsed response is "\x89PNG\r\n\x1A\n\x00\x00\x00\rIHDR\x00\x00\x05\xA0\x00\x00\x05\xA0\b\x02\x00\x00\x00'\xC2s\x9F\x00\x01\x00\x00IDATx\x9C\xEC\xFD[\x93$\xD9u\xE7\x8B\xFD\xD7\xF2\xAC\xCA\xEA\xAA\x06\x01j\xCE\x90U\r\x1C\xE9\x98I/2=\xC8Ht\x17\xCD\xF4A\bp83\x94t\x8E$3}#\x9D3:g\x86\x04\x89\xA6\x1D\x99\x1E\xF4\r\xC0\xE1H\x86jp(\xD3\x9B\xDEd\x04\xD0] n\x8D\xEE\xCA\xAC\x88\xCC\xF0\xB5\xF4\xB0\xF6\xCD/q\xCB\xCC\xC8\xC8\xA8\xFA\xFF:;+#\xC2}\xFB\xF6\xED\xDB=\xF6\xFE\xEFu\x11w\a!\x84\x10B\b!\x84\x10B\xC8)\xA3\xC7\xAE\x00!\x84\x10B\b!\x84\x10B\xC8m\xA1\xC0A\b!\x84\x10B\b!\x84\x90\x93\x87\x02\a!\x84\x10B\b!\x84\x10BN\x1E\n\x1C\x84\x10B\b!\x84\x10B\b9y(p\x10B\b!\x84\x10B\b!\xE4\xE4\xA1\xC0A\b!\x84\x10B\b!\x84\x90\x93\x87\x02\a!\x84\x10B\b!\x84\x10BN\x1E\n\x1C\x84\x10B\b!
How do I fix this and save the base64 encoded image that is returned?

path name contains null byte for image url while existing

I'm using the following code to get the width of image using Dimensions Gem.
file_path = "http://localhost:3000/uploads/resize/avatar/25/ch05.jpg"
width = Dimensions.width(open(file_path).read)
When I put the image url in url bar it renders the image in browser. what I'm trying to do is to get the width of image. so can anyone know what I'm doing wrong?
So your issue is that Dimensions requires a file path to determine the width of the image. open will return a StringIO and open(...).read will return a String both will fail when using File.open.
Dimensions#width
def width(path)
io_for(path).width
end
Dimensions#io_for
def io_for(path)
Dimensions(File.open(path, "rb")).tap do |io|
io.read
io.close
end
end
To work around this you can download the image to a Tempfile and then use that path to pass to Dimensions.width like so
path = "http://localhost:3000/uploads/resize/avatar/25/ch05.jpg"
t = Tempfile.new # you could add a name but it doesn't matter
t.write(open(path).read) # write the image to the Tempfile
t.close # must close the file before reading it
width = Dimensions.width(t.path) # pass the Tempfile path to Dimensions
t.unlink # deletes the Tempfile
We can make this look a little cleaner like so:
def get_width_of_url_image(url)
t = Tempfile.new.tap do |f|
f.write(open(url).read)
f.close
end
width = Dimensions.width(t.path)
t.unlink and width
end
get_width_of_url_image("https://www.google.com/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png")
#=> 272

How do I figure out the mime type without writing a file?

This is with Rails 5 and ruby-filemagic. Currently I'm saving an uploaded image to my database in addition to its mime type. I have this code in my controller
def create
#person = Person.new(person_params)
if #person.image
cur_time_in_ms = DateTime.now.strftime('%Q')
file_location = "/tmp/file#{cur_time_in_ms}.ext"
File.binwrite(file_location, #person.image.read)
fm = FileMagic.new(FileMagic::MAGIC_MIME)
#person.content_type = fm.file(file_location, true)
end
if #person.save
redirect_to #person
else
# This line overrides the default rendering behavior, which
# would have been to render the "create" view.
render "new"
end
end
The thing that bothers me with this approach is taht I have to write a file to the file system before figuring out its mime type. That seems wasteful. How can I figure out the mime type without creating a file first?
Edit: In response to the answer given, I rearranged things, but now the "content_type" becomes "application/x-empty" even when I upload a valid png file. Here's the code
if #person.image
cur_time_in_ms = DateTime.now.strftime('%Q')
file_location = "/tmp/file#{cur_time_in_ms}.ext"
File.binwrite(file_location, #person.image.read)
file = File.open(file_location, "rb")
contents = file.read
# Scale image appropriately
#img = Magick::Image::read(file_location).first
#person.content_type = FileMagic.new(FileMagic::MAGIC_MIME).buffer(#person.image.read, true)
#person.image = contents
Assuming you are uploading file via html form, IO object should already have mime type, you can get it like that:
mime = params[:file].content_type
Try using the buffer method instead of file i.e. FileMagic.new(FileMagic::MAGIC_MIME).buffer(#person.image.read, true)

Check if SVG exists in Rails

I have the following code as a helper that I want to use to check if an image exists and if so then return it's raw SVG data:
def svg(name: 'default')
file = asset_path('images/' + name + '.svg')
if( File.file?(file) )
file = File.open(file, 'rb')
contents = file.read
file.close
contents.html_safe
end
end
However the file always comes back false... (the svg exists!)
Is the way I'm getting the file incorrect?

How to handle a file_as_string (generated by Prawn) so that it is accepted by Carrierwave?

I'm using Prawn to generate a PDF from the controller of a Rails app,
...
respond_to do |format|
format.pdf do
pdf = GenerateReportPdf.new(#object, view_context)
send_data pdf.render, filename: "Report", type: "application/pdf", disposition: "inline"
end
end
This works fine, but I now want to move GenerateReportPdf into a background task, and pass the resulting object to Carrierwave to upload directly to S3.
The worker looks like this
def perform
pdf = GenerateReportPdf.new(#object)
fileString = ???????
document = Document.new(
object_id: #object.id,
file: fileString )
# file is field used by Carrierwave
end
How do I handle the object returned by Prawn (?????) to ensure it is a format that can be read by Carrierwave.
fileString = pdf.render_file 'filename' writes the object to the root directory of the app. As I'm on Heroku this is not possible.
file = pdf.render returns ArgumentError: string contains null byte
fileString = StringIO.new( pdf.render_file 'filename' ) returns TypeError: no implicit conversion of nil into String
fileString = StringIO.new( pdf.render ) returns ActiveRecord::RecordInvalid: Validation failed: File You are not allowed to upload nil files, allowed types: jpg, jpeg, gif, png, pdf, doc, docx, xls, xlsx
fileString = File.open( pdf.render ) returns ArgumentError: string contains null byte
....and so on.
What am I missing? StringIO.new( pdf.render ) seems like it should work, but I'm unclear why its generating this error.
It turns out StringIO.new( pdf.render ) should indeed work.
The problem I was having was that the filename was being set incorrectly and, despite following the advise below on Carrierwave's wiki, a bug elsewhere in the code meant that the filename was returning as an empty string. I'd overlooked this an assumed that something else was needed
https://github.com/carrierwaveuploader/carrierwave/wiki/How-to:-Upload-from-a-string-in-Rails-3
my code ended up looking like this
def perform
s = StringIO.new(pdf.render)
def s.original_filename; "my file name"; end
document = Document.new(
object_id: #object.id
)
document.file = s
document.save!
end
You want to create a tempfile (which is fine on Heroku as long as you don't expect it to persist across requests).
def perform
# Create instance of your Carrierwave Uploader
uploader = MyUploader.new
# Generate your PDF
pdf = GenerateReportPdf.new(#object)
# Create a tempfile
tmpfile = Tempfile.new("my_filename")
# set to binary mode to avoid UTF-8 conversion errors
tmpfile.binmode
# Use render to write the file contents
tmpfile.write pdf.render
# Upload the tempfile with your Carrierwave uploader
uploader.store! tmpfile
# Close the tempfile and delete it
tmpfile.close
tmpfile.unlink
end
Here's a way you can use StringIO like Andy Harvey mentioned, but without adding a method to the StringIO intstance's eigenclass.
class VirtualFile < StringIO
attr_accessor :original_filename
def initialize(string, original_filename)
#original_filename = original_filename
super(string)
end
end
def perform
pdf_string = GenerateReportPdf.new(#object)
file = VirtualFile.new(pdf_string, 'filename.pdf')
document = Document.new(object_id: #object.id, file: file)
end
This one took me couple of days, the key is to call render_file controlling the filepath so you can keep track of the file, something like this:
in one of my Models e.g.: Policy i have a list of documents and this is just the method for updating the model connected with the carrierwave e.g.:PolicyDocument < ApplicationRecord mount_uploader :pdf_file, PdfDocumentUploader
def upload_pdf_document_file_to_s3_bucket(document_type, filepath)
policy_document = self.policy_documents.where(policy_document_type: document_type)
.where(status: 'processing')
.where(pdf_file: nil).last
policy_document.pdf_file = File.open(file_path, "r")
policy_document.status = 's3_uploaded'
policy_document.save(validate:false)
policy_document
rescue => e
policy_document.status = 's3_uploaded_failed'
policy_document.save(validate:false)
Rails.logger.error "Error uploading policy documents: #{e.inspect}"
end
end
in one of my Prawn PDF File Generators e.g.: PolicyPdfDocumentX in here please note how im rendering the file and returning the filepath so i can grab from the worker object itself
def generate_prawn_pdf_document
Prawn::Document.new do |pdf|
pdf.draw_text "Hello World PDF File", size: 8, at: [370, 462]
pdf.start_new_page
pdf.image Rails.root.join('app', 'assets', 'images', 'hello-world.png'), width: 550
end
end
def generate_tmp_file(filename)
file_path = File.join(Rails.root, "tmp/pdfs", filename)
self.generate_prawn_pdf_document.render_file(file_path)
return filepath
end
in the "global" Worker for creating files and uploading them in the s3 bucket e.g.: PolicyDocumentGeneratorWorker
def perform(filename, document_type, policy)
#here we create the instance of the prawn pdf generator class
pdf_generator_class = document_type.constantize.new
#here we are creating the file, but also `returning the filepath`
file_path = pdf_generator_class.generate_tmp_file(filename)
#here we are simply updating the model with the new file created
policy.upload_pdf_document_file_to_s3_bucket(document_type, file_path)
end
finally how to test, run rails c and:
the_policy = Policies.where....
PolicyDocumentGeneratorWorker.new.perform('report_x.pdf', 'PolicyPdfDocumentX',the_policy)
NOTE: im using meta-programming in case we have multiple and different file generators, constantize.new is just creating new prawn pdf doc generator instance so is similar to PolicyPdfDocument.new that way we can only have one pdf doc generator worker class that can handle all of your prawn pdf documents so for instance if you need a new document you can simply PolicyDocumentGeneratorWorker.new.perform('report_y.pdf', 'PolicyPdfDocumentY',the_policy)
:D
hope this helps someone to save some time

Resources