Uploading a simple CSV file to a controller - ruby

I'm trying to upload a CSV file to my controller and access the data within.
Here's the code for the Controller:
class DatabaseImporterController < ApplicationController
def index
end
def import
# Receive the uploaded CSV file and import to the database.
csv_file = params[:csv_file]
logger.debug { csv_file.inspect }
#CSV.foreach("parse.csv") do |row|
#end
render :template => "database_importer/index"
end
end
And the ouput of logger.debug:
{"utf8"=>"✓",
"authenticity_token"=>"Z4+XlmrcH+8JPL6Yq52ymVOMfiGEI9mN8LuoxoBLp8M=",
"csv_file"=>#<ActionDispatch::Http::UploadedFile:0x007feca81b3fb8 #original_filename="Install-Linux-tar.txt", #content_type="text/plain", #headers="Content-Disposition: form-data; name=\"csv_file\"; filename=\"Install-Linux-tar.txt\"\r\nContent-Type: text/plain\r\n", #tempfile=#<File:/tmp/RackMultipart20121229-10294-1ngf634>>,
"commit"=>"Import CSV Car Database",
"controller"=>"database_importer",
"action"=>"import"}
Accoding to the documentation on the official Ruby on Rails page:
The object in the params hash is an instance of a subclass of IO. Depending on the size of the uploaded file it may in fact be a StringIO or an instance of File backed by a temporary file.
As I understand it, the uploaded file is somewhere in my disc (in my Heroku instance) and I can access it temporarily.
How can I access the file? I've tried the following and I get and error message:
csv_file = params[:csv_file][:tempfile] # This is how I try to get the file page of the temporary file.
undefined method `[]' for #<ActionDispatch::Http::UploadedFile:0x007fecb02103c8>

You want to call tempfile, not [:tempfile]:
params[:csv_file].tempfile
You can see all the methods available in the docs for ActionDispatch::Http::UploadedFile.

Related

Zero Bytes Sent When Sending Axlsx Gem Generated File From Sinatra

Attempting to prompt a download window and stream an XLSX file using Ruby Sinatra and the AXLSX gem, my excel file serializes successfully to local file, so I know its a valid excel doc, but I need it to transfer content to the end user. There haven't been any docs online with examples of AXLS and Sinatra used together, only rails. Help is appreciated!
class Downloads < Sinatra::Base
get '/downloads/report' do
## ...
Axlsx::Package.new do |p|
p.workbook.add_worksheet(name: 'tab name') do |sheet|
## ...
end
content_type 'application/xlsx'
attachment 'cost-code-dashboard.xlsx'
p.to_stream # unsuccessful
# p.to_stream.read # unsuccessful as well
end
end
end
I have also tried the following snippet unsuccessfully
Axlsx::Package.new do |p|
## ...
send_file p.to_stream.read, type: "application/xlsx", filename: "cost-code-dashboard.xlsx"
end
It appears that the issue had everything to do with how Axlsx::Package.new was called, the helper functions were not available inside Axlsx, the following solution worked - online documentation said that the below content_type was better
get '/downloads' do
content_type :'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
p = Axlsx::Package.new
p.workbook.add_worksheet(name: 'Test') do |sheet|
sheet.add_row ['Hello world']
end
p.to_stream
end

How can I use rubyzip to attach files in a zip archive to an ActiveStorage object Rails 5

I have a model with one attachment that uses ActiveStorage:
class ProofreadDocument < ApplicationRecord
has_one_attached :file
end
I am working on a rake task to attach files to a proofread_document.
The files are compressed into a zip archive.
I understand that I can attach the files as follows from reading the ActiveStorage docs:
#proofread_document.file.attach(io: File.open('/path/to/file'), filename: 'file.pdf')
I have the following in my rake task:
Zip::File.open(args.path_to_directory) do |zipfile|
zipfile.each do |file|
proofread_document = ProofreadDocument.new()
proofread_document.file.attach(io: file.get_input_stream.read, filename: file.name)
proofread_document.save
end
end
This produces the following error:
NoMethodError: undefined method `read' for #<String:0x007f8d894d95e0>
I need to read the contents of each file one at at time to attach them to the proofread_document instance. How can I do this?
I was able to succeed by wrapping the input stream in a StringIO object as follows:
self.file.attach(io: StringIO.new(zip_entry.get_input_stream.read), filename: zip_entry.name, content_type: MS_WORD_CONTENT_TYPE)

Sinatra JSON.parse undefined method when moving helper to another file

I have a route helper that works fine when it's in a "helpers do" block in my route file, but when I move the helper to my helpers.rb file, I get an error. Here is the helper:
def processPutRequest(patient_id, request, _class, foreign_key, route_fragment)
p = Patient.find(patient_id)
data = request.body.read
puts 'request body' + data
data = JSON.parse(data)
host_with_port = request.host_with_port
puts 'hash data: ' + data.inspect
saveListData(params[:id], data, _class, foreign_key)
url = {'url' => "http://#{host_with_port}/patients/#{params[:id]}#{route_fragment}"}.to_json
Rack::Response.new(url)
end
Here is the logging when the helper is in the route file:
request body{"list_data":[{"id":8440,"value":"Removal of ear wax (procedure)"},{"id":9827,"value":"Foreign body in nose (disorder)"}]}
hash data: {"list_data"=>[{"id"=>8440, "value"=>"Removal of ear wax (procedure)"}, {"id"=>9827, "value"=>"Foreign body in nose (disorder)"}]}
And when I move it to the helpers file:
request body{"list_data":[{"id":8440,"value":"Removal of ear wax (procedure)"},{"id":9827,"value":"Foreign body in nose (disorder)"}]}
NoMethodError - undefined method `parse' for Sinatra::JSON:Module:
Am I missing something really simple?
EDIT, got the debugger working. "data" after request.body.read in routes file:
"{"list_data":[{"id":8440,"value":"Removal of ear wax (procedure)"},{"id":9827,"value":"Foreign body in nose (disorder)"}]}"
in helpers file:
"{"list_data":[{"id":8440,"value":"Removal of ear wax (procedure)"},{"id":9827,"value":"Foreign body in nose (disorder)"}]}"
So the content looks identical to me. I can literally cut and paste this method between the two files, it works fine in the routes file, fails with undefined method parse in the helpers file. I'm guessing I've somehow defined that module improperly or have a dandling or missing character, but RubyMine is showing no errors, and the method does at least partially execute, so the method is getting sourced.
Complete helpers file:
module Sinatra
module DynFormat
CONTENT_TYPES={'xml' => 'text/xml','json' => 'application/json'}
def dynamicFormat(data,format=params[:format])
content_type CONTENT_TYPES[format], :charset => 'utf-8'
case format
when 'xml'
data.to_xml
when 'json'
data.to_json
end
end
end
helpers DynFormat
module RouteHelpers
def processPutRequest(patient_id, request, _class, foreign_key, route_fragment)
p = Patient.find(patient_id)
data = request.body.read
puts 'request body' + data
data = JSON.parse(data)
host_with_port = request.host_with_port
puts 'hash data: ' + data.inspect
saveListData(params[:id], data, _class, foreign_key)
url = {'url' => "http://#{host_with_port}/patients/#{params[:id]}#{route_fragment}"}.to_json
Rack::Response.new(url)
end
def saveListData(patient_id, list_data, _class, foreign_key)
p = Patient.find(patient_id)
_class = eval _class
list_data = list_data['list_data']
list_data.each do |x|
_class.create(:patient_id => patient_id, foreign_key => x['id'] )
end
end
end
helpers RouteHelpers
end
When you put your method in the external file, you are putting into a module under the Sinatra namespace:
module Sinatra
module DynFormat
#...
end
module RouteHelpers
def processPutRequest(patient_id, request, _class, foreign_key, route_fragment)
# code that refers to JSON...
end
end
end
When your method calls JSON.parse it ends up finding the Sinatra::JSON module, not the top level JSON module that you intended, and so you get the error undefined method `parse' for Sinatra::JSON:Module.
When you include the method in a helpers block in your main file the method isn’t defined under the Sinatra module, so JSON refers to the correct top level module.
If you need to include Sinatra::JSON, you can explicitly refer to the top level module using :::
data = ::JSON.parse(data)
Also note that you don’t necessarily need to define your helper modules under Sinatra (though the docs do suggest you do). You can move them out of the Sinata module, and then use e.g. Sinatra.helpers RouteHelpers to register them as helpers.
I was requiring both json and sinatra/json. No idea why the last one didn't win all the time, but removing sinatra/json resolved the issue. If something else breaks, I'll spend the time deciding between the two.

Who can explain how this code block works: Carrierwave MD5 as filename

I am using the Carrierwave gem with a Sinatra app to upload and store images.
I am following How to: Use file's MD5 as filename and everything works as expected. However I do not understand how the following piece of code provided on the how-to page works:
class ImageUploader < CarrierWave::Uploader::Base
storage :file
def md5
chunk = model.send(mounted_as)
#md5 ||= Digest::MD5.hexdigest(chunk.read)
end
def filename
#name ||= "#{md5}#{File.extname(super)}" if super
end
end
I in particular do not understand what model.send(mounted_as)does, what the ||= operator means, and why the if super conditional is used (and what it does).
Could somebody please explain this to me?
Say, for example the model is Person and the ImageUploader is mounted as avatar.
class Person < ActiveRecord::Base
mount_uploader :avatar, ImageUploader
end
Then, the md5 method would be calling something to the affect of chunk = person.avatar and using this to calculate the hash of the file contents, which you want for the name.
The filename method checking to see if there is a #filename instance variable, as defined in the CarrierWave::Uploader::Store class.
class CarrierWave::Uploader::Store
def filename
#filename
end
end
Then calling this again to get the filename extension to use in the constructed filename. The #name variable is then just a temporary cache of the name, so that future calls to the method do not require the whole thing to be calculated again.
Edit:
The carrierwave uploader has methods/instance variables for the model (eg Person instance) and declared mount point in the model (eg avatar). These are from the mount_uploader declaration in your active record model.
module CarrierWave::Uploader::Mountable
attr_reader :model, :mounted_as
def initialize(model=nil, mounted_as=nil)
#model = model
#mounted_as = mounted_as
end
end
These are used for various things, as well as being made available for us to do things just such as you are trying. It is just an abstract way to call person.avatar, which returns the file (File instance not string path). This is then read into the MD5 lib, which gives the hexdigest.
Rewriting this in more plain terms
class ImageUploader < CarrierWave::Uploader::Base
def md5
uploaded_file = model.send(mounted_as) # person.avatar (File instance)
#md5 ||= Digest::MD5.hexdigest(uploaded_file.read) # hexdigest of file content
end
end

Can't upload image to Cloudinary from Sinatra

I'm trying to upload an image to Cloudinary from within my Sinatra web app. I
got it saving the image locally by using CarrierWave and I know Cloudinary and
CarrierWave play nicely with each other but I've tried everything and no dice.
I've followed the docs but they are very Rails-centric. Damn you Rails!
# Model - User.rb
class ImageUploader < CarrierWave::Uploader::Base
include Cloudinary::CarrierWave
process convert: 'png'
version :face do
cloudinary_transformation gravity: :face
end
end
class User
include DataMapper::Resource
attr_accessor :image
property.............
mount_uploader :image, ImageUploader
end
# Config - app.rb
require 'carrierwave/datamapper'
require 'cloudinary'
# DataMapper code ...
# Some routes ...
post '/add' do
user = user.create(name: params[:name])
Cloudinary::Uploader.upload(params[:image], api_key: 'API_KEY', api_secret: 'API_SECRET', cloud_name: 'CLOUD_NAME')
# View (haml)
%head
%script{src: "//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"}
%script{src: "js/cloudinary_js/jquery.ui.widget.js"}
%script{src: "js/cloudinary_js/jquery.iframe-transport.js"}
%script{src: "js/cloudinary_js/jquery.fileupload.js"}
%script{src: "js/cloudinary_js/jquery.cloudinary.js"}
# snip snip ...
%form{action: "/add", method: "POST", enctype: "multipart/form-data"}
%input{type: "file", name: "image"}
%button Submit
The error I'm getting when trying to submit the form is:
TypeError at /add
no implicit conversion of Hash into String
The line it gives me that is causing the error is the Cloudinary uploader line in the post method.
Here's my params[:image] hash
{:filename=>"batman_robin.jpg", :type=>"image/jpeg", :name=>"image", :tempfile=>#<Tempfile:/var/folders/rv/2w8gpstn4hb2lb__27n27ksh0000gn/T/RackMultipart20131223-36484-tgmaba>, :head=>"Content-Disposition: form-data; name=\"image\"; filename=\"batman_robin.jpg\"\r\nContent-Type: image/jpeg\r\n"}
In the docs it mentions a file signature then states that CarrierWave handles this. I think my problem lies with the upload form.
Where am I going wrong with this? Anyone know what 'cl_image_upload_tag' generates and what are Cloudinary's "special attributes" that I need to add to my file upload tag?
UPDATE: So I figured that and I can actually just pass the image directly like so:
Cloudinary::Uploader.upload(params[:image][:filename] ...
However it now says
No such file or directory - batman_robin.jpg
which is strange. I've hardcoded a URL in there and it gets uploaded to Cloudinary. Any ideas? Also, any idea about the Cloudinary jQuery upload?
params[:image] is a hash but Cloudinary::Uploader.upload expects a string as the first argument. All the examples in the docs give a name or a URL, so pass the :filename from the params, e.g.
post '/add' do
user = user.create name: params[:name]
image_meta = params[:image]
filename = image_meta.delete :filename
Cloudinary::Uploader.upload filename, {api_key: 'API_KEY', api_secret: 'API_SECRET', cloud_name: 'CLOUD_NAME'}.merge(image_meta)
I'd also put all those default settings into a settings hash (and have them read in from the environment if they're secret), e.g.
config do
set :cloudinary_api, {api_key: ENV['API_KEY'],
api_secret: ENV['API_SECRET'],
cloud_name: ENV['CLOUD_NAME']}
end
then the call becomes…
Cloudinary::Uploader.upload filename, settings.cloudinary_api.merge(image_meta)
Instead of the filename you need to pass the actual content which is hold in Tempfile So passing Cloudinary::Uploader.upload(params[:image][:tempfile]) should work.

Resources