Ruby On Rails: upload a file to a specific folder - ruby-on-rails

I am using this file uploader example for Ruby on Rail.
Now the files are saved by their IDs into: public->system->uploads->uploads->000->000.
I need it to be saved into such system of folders: public->system->Files->Types(JPG OR PDF)
The files have to be saved not according to their IDs but according to their types.
Example:
All jpg files should be saved into:
public->system->Files->JPG
All PDF files should be saved into:
public->system->Files->PDF
I have already created the folders, I need only to specify a path where the files have to be saved.
That is a code from uploads_controller.rb
def create
p_attr=params[:upload]
p_attr[:upload] = params[:upload][:upload].first if params[:upload][:upload].class == Array
#upload = Upload.new(p_attr)
respond_to do |format|
if #upload.save
format.html {
render :json => [#upload.to_jq_upload].to_json,
:content_type => 'text/html',
:layout => false
}
format.json { render json: [#upload.to_jq_upload].to_json, status: :created, location: #upload }
else
format.html { render action: "new" }
format.json{ render json: {name:(#upload.upload_file_name).split(".").first ,error: #upload.errors.messages[:upload_file_name]}, :status =>422}
end
end
end
Thanks in advance

I'd recommend switching to carrierwave uploader. There are config options , which are going to help you in separating files by type . I mean , in this configuration in carrierwave uploader :
def store_dir
"uploads/#{Rails.env}/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
end
you can alter the 'model.id' part with a method , naming the image by type .

You can manually do this using regex. So it'd be something like:
type = p_attr.filename.match(/^*\w{3,}$/).to_s
filename being whatever its called within p_attr. So that would give you the file type ending as a string. According to the paperclip gem you can set a different path and url by setting them as a param:
path = "#{Rails.env}/public/system/FILE/#{type}"
#upload = Upload.new(p.attrs, path: path, url: path)
Not 100% sure on the upload line, but something like that should be able to override the paperclip default.
For more information check the paperclip readme section about storage. It explains about overriding default path.

Changing Paperclip File Storage Location

Related

When you upload multiple files using ActiveStorage, is it possible to determine what was just uploaded?

I have a model (let's call it Message) that's using Active Storage with has_many_attached to store multiple uploads through a Javascript routine. I'd like to know the URL of the uploads as soon as these are received and processed by the Update action in my controller. Ideally, the response would be in the form of a JSON string, containing the URL of the file that is just uploaded by the Javascript routine.
Other than having to loop through all the uploads, is there a way to obtain this information as they're uploaded?
I'm not using the DirectUpload javascript suggested in the Active Storage guide, in case you're wondering. The Javascript I'm using sends the files to a custom PATCH action /message/:id/new_upload (where :id is the ID of the Message record), with the BLOB file as its only parameter.
I've tried to use something like this:
def new_upload
#message.uploads.attach(params[:uploads])
respond_to do |format|
format.json {render json: { location: "The URL would go here"} }
end
end
But the attach line at the beginning doesn't return anything (obviously), all it is is doing is saving the attachments. Is there anything I can use to figure out what was just uploaded?
UPDATE
I've managed to make it "work" by doing something like this:
def new_upload
#message.uploads.attach(params[:uploads])
upload_url = rails_blob_url(#message.uploads.blobs.last)
respond_to do |format|
format.json {render json: { location: upload_url} }
end
end
However, this only works for one attachment (the last). Thankfully I only require to upload one file at a time, but I'd prefer to infer the URL by something other than checking the last upload.

Google Ruby API Client Create File Response

I am currently working on a Rails project where a file gets uploaded to Drive. I am able to get files uploaded to Drive however am wondering how to get a response that contains the file ID, link, etc. Do I need to use list for this? Any help would be greatly appreciated.
def create
#essay = Essay.new(params.require(:essay).permit(:course_name))
# Uploaded File
uploaded_io = params[:essay][:essay_draft]
# Save to a temporary folder
Tempfile.open(uploaded_io.original_filename, Rails.root.join('private', 'tmp')) do |f|
# Write using UTF-8 encoding
f.write(uploaded_io.read.force_encoding("UTF-8"))
# Close the file
f.close
# Gotta unlink to delete the temp file
f.unlink
end
# Set Metadata to be sent to Google Drive
file_metadata = {
name: uploaded_io.original_filename,
mime_type: 'application/vnd.google-apps.document'
}
# Call method which will upload the actual file to Drive
#drive.create_file(file_metadata,
fields: 'id',
upload_source: uploaded_io.path,
content_type: 'text/doc')
if #essay.save
redirect_to #essay
else
render :new
end
end
This is what I have in my create method.
def create
#essay = Essay.new(params.require(:essay).permit(:course_name))
# Uploaded File
uploaded_io = params[:essay][:essay_draft]
# Set Metadata to be sent to Google Drive
file_metadata = {
name: uploaded_io.original_filename,
mime_type: 'application/vnd.google-apps.document'
}
# Call method which will upload the actual file to Drive
#file = #drive.create_file(file_metadata,
fields: 'id, web_view_link',
upload_source: uploaded_io.path,
content_type: 'text/doc')
if #essay.save
render :show
else
render :new
end
end
I was then able to put the following into my view:
<%= #file.id %>
<%= #file.web_view_link %>

Rails automatically adds ".csv" to download name for format CSV, but not for others (e.g. ".xlsx")

Users can download data from my Rails project in CSV or XLSX format.
Here's the action:
def index
respond_to do |format|
format.xlsx { render xlsx: 'index', filename: filename_for_export(#project, export_type, :xlsx) }
format.csv { render csv: collection, filename: filename_for_export(#project, export_type, :csv) }
end
end
private
def filename_for_export(project, type, format)
"#{project.customer} - #{project.name} (#{type}, #{t 'org.name'}, #{Date.today.to_s :db}).#{format}"
end
Interestingly, when opening .csv, Rails seems to add .csv again to the filename (which already has the format in it).
When opening .xlsx, this doesn't happen:
Interestingly, when removing the file extension from the generated name, then both have one single correct extension, although XLSX shouldn't have any extension now (in my opinion).
It looks like you are forcing the extension out for xlsx files by using render xlsx: 'index'.
If you change that line to render xlsx: 'index.xlsx', you should get a consistent behavior. I imagine that you won't have to set the extension in filename_for_export anymore.

rails excel mime-type - how to change default filename?

I followed the http://railscasts.com/episodes/362-exporting-csv-and-excel
and set up an Excel Download in my Rails application.
My controller code looks like this:
def show
#project = Project.find(params[:id])
#project.tasks.order(:name)
respond_to do |format|
format.html
format.json { render json: #project }
format.xls
end
end
and in my view I create the link to download the excel file like this:
.dl_xls= link_to "Download xls", url_for(:format => 'xls')
Now the generated excel file is always named like the id of the Project record, e.g. 80.xls
Is there any way to change this behaviour and give it a custom name?
Thank you..
I believe your answer is here: in rails, how to return records as a csv file
Use headers to set the filename.
headers["Content-Disposition"] = "attachment; filename=\"#{filename}\""
def index
#tabulars = Tabular.all
filename = "data_users.xls"
respond_to do |format|
format.html
format.xls { headers["Content-Disposition"] = "attachment; filename=\"#{filename}\"" }
end
end
This link more detail change the file name excel
I expect what you are actually seeing there is the name of the view sans .erb, not necessarily the controller action.
If you want that level of control there are three things you can do.
Use the send_data call from your controller with tab separated data as shown in the rails cast with the filename: option
e.g.
class ProductsController < ApplicationController
def index
#products = Product.order(:name)
respond_to do |format|
format.html
format.csv { send_data #products.to_csv }
format.xls { send_data #products.to_csv(col_sep: "\t"), filename: 'your_file_name.xls'}
end
end
end
There are problems with this approach as well as the old propriety spreadsheetML language that the railscast introduces, but if your user base is locked into MS-OFFICE, I dont think anyone will notice.
Alternatively, you can use a gem like acts_as_xlsx or axlsx_rails that consume the axlsx gem. Those tools generate validated xlsx data (also known as Office Open XML / ECMA-376 - or what MS has been using since office 2007...), and have fairly good interoperability with other modern spreadsheet software like Numbers, GoogleDocs, LibraOffice. I am sure you noticed all the comments related to this in the railscast.
I know, because I am the author or axlsx, and those limitations, and the lack of styling, charts and validation where what drove me to author axlsx in the first place.
More Info:
axlsx: https://github.com/randym/axlsx
acts_as_xlsx:
http://axlsx.blogspot.jp/2011/12/using-actsasxlsx-to-generate-excel-data.html
Write your own responder / renderer
axlsx_rails is also a great example on how to author your own renderer and responder so that you can use the standard rails view, but rename the file that gets downloaded.
https://github.com/straydogstudio/axlsx_rails/blob/master/lib/axlsx_rails/action_controller.rb

Sending files to a Rails JSON API

I know there are questions similar to this one, but I've not found a good answer yet. What I need to do is send a description of an object to one of my create methods, which includes some different attributes including one called :image, a paperclip attachment:
has_attached_file :image
Now I've read that sending the image could be done straight in JSON by encoding and decoding the image as base64, but that feels like a dirty solution to me. There must be better ways.
Another solution is sending a multipart/form-data request, much like the one LEEjava describes here. The problem with that one is that the request params are not interpreted correctly in Rails 3.2.2, and JSON.parse spits out an error when it tries to parse the params, or perhaps it is Rails that is misinterpreting something.
Started POST "/api/v1/somemodel.json?token=ZoipX7yhcGfrWauoGyog" for
127.0.0.1 at 2012-03-18 15:53:30 +0200 Processing by Api::V1::SomeController#create as JSON Parameters: {"{\n
\"parentmodel\": {\n \"superparent_id\": 1,\n
\"description\": \"Enjoy the flower\",\n \"\": "=>{"\n
{\n \"someattribute\": 1,\n
\"someotherattribute\": 2,\n \"image\": \"image1\"\n
}\n "=>{"\n }\n}"=>nil}}, "token"=>"ZoipX7yhcGfrWauoGyog"}
It is quite hard to read that, sorry. JSON.parse(params[:parentmodel]) is not possible here, and I can't JSON.parse(params) either because of the token attribute, JSON.parse(params) throws this error:
TypeError (can't convert ActiveSupport::HashWithIndifferentAccess into String)
Which leads me to believe I'm either approaching this problem totally wrong, or I'm just doing something. Either way, we can be sure that I'm wrong about something. :)
Is there a better way to do this? Can someone point me to any guide/tutorial, or write an answer describing how I should approach this?
Thank you in advance
UPDATE:
So I've actually got it working now, but only in tests. I'm not totally sure how this works, but perhaps someone can fill in the gaps for me? This is part of the test code (the image: fixture_file_upload(...) is the important part).
parts_of_the_object = { someattribute: 0, someotherattribute: 0, image: fixture_file_upload('/images/plot.jpg', 'image/jpg') }
My params[] looks like a normal HTML form was submitted, which is strange (and awesome):
Parameters: {"superparentid"=>"1", "plots"=>[{"someattribute"=>"0", "someotherattribute"=>"0", "image"=>#<ActionDispatch::Http::UploadedFile:0x007f812eab00e8 #original_filename="plot.jpg", #content_type="image/jpg", #headers="Content-Disposition: form-data; name=\"plots[][image]\"; filename=\"plot.jpg\"\r\nContent-Type: image/jpg\r\nContent-Length: 51818\r\n", #tempfile=#<File:/var/folders/45/rcdbb3p50bl2rgjzqp3f0grw0000gn/T/RackMultipart20120318-1242-1cn036o>>}], "token"=>"4L5LszuXQMY6rExfifio"}
The request is made just like and post request is made with rspec:
post "/api/v1/mycontroller.json?token=#{#token}", thefull_object
So I've got it all working. I just don't know how exactly it works! I want to be able to create a response like this by myself too, not only from RSpec. :-)
I was actually having a terrible time with this question yesterday to do something very similar. In fact, I wrote the question: Base64 upload from Android/Java to RoR Carrierwave
What it came down to was creating that uploaded image object in the controller and then injecting it back into the params.
For this specific example, we are taking a base64 file (which I assume you have, as JSON doesn't support embeded files) and saving it as a temp file in the system then we are creating that UploadedFile object and finally reinjecting it into the params.
What my json/params looks like:
picture {:user_id => "1", :folder_id => 1, etc., :picture_path {:file => "base64 awesomeness", :original_filename => "my file name", :filename => "my file name"}}
Here is what my controller looks like now:
# POST /pictures
# POST /pictures.json
def create
#check if file is within picture_path
if params[:picture][:picture_path]["file"]
picture_path_params = params[:picture][:picture_path]
#create a new tempfile named fileupload
tempfile = Tempfile.new("fileupload")
tempfile.binmode
#get the file and decode it with base64 then write it to the tempfile
tempfile.write(Base64.decode64(picture_path_params["file"]))
#create a new uploaded file
uploaded_file = ActionDispatch::Http::UploadedFile.new(:tempfile => tempfile, :filename => picture_path_params["filename"], :original_filename => picture_path_params["original_filename"])
#replace picture_path with the new uploaded file
params[:picture][:picture_path] = uploaded_file
end
#picture = Picture.new(params[:picture])
respond_to do |format|
if #picture.save
format.html { redirect_to #picture, notice: 'Picture was successfully created.' }
format.json { render json: #picture, status: :created, location: #picture }
else
format.html { render action: "new" }
format.json { render json: #picture.errors, status: :unprocessable_entity }
end
end
end
The only thing left to do at this point is to delete the tempfile, which I believe can be done with tempfile.delete
I hope this helps with your question! I spent all day looking for a solution yesterday, and everything I have seen is a dead end. This, however, works on my test cases.
TomJ gave a good answer, but at least in Rails 3/Ruby 1.9 there are some minor holes.
First, don't attempt to call [] on what might be an UploadedFile object in your params object. Make sure you check that it .is_a?(Hash) first, for example.
Also, make sure you tempfile.rewind() after you write, otherwise you'll get files with 0 length.
The :original_filename key in the parameters to the constructor of UploadedFile is unnecessary/unused. On the other hand, you may want to provide a :type key. An easy way to find the value for type is mime_type = Mime::Type.lookup_by_extension(File.extname(original_filename)[1..-1]).to_s
Here is a version with the changes applied:
# POST /pictures
# POST /pictures.json
def create
#check if file is within picture_path
if params[:picture][:picture_path].is_a?(Hash)
picture_path_params = params[:picture][:picture_path]
#create a new tempfile named fileupload
tempfile = Tempfile.new("fileupload")
tempfile.binmode
#get the file and decode it with base64 then write it to the tempfile
tempfile.write(Base64.decode64(picture_path_params["file"]))
tempfile.rewind()
original_filename = picture_path_params["original_filename"]
mime_type = Mime::Type.lookup_by_extension(File.extname(original_filename)[1..-1]).to_s
#create a new uploaded file
uploaded_file = ActionDispatch::Http::UploadedFile.new(
:tempfile => tempfile,
:filename => picture_path_params["filename"],
:type => mime_type)
#replace picture_path with the new uploaded file
params[:picture][:picture_path] = uploaded_file
end
#picture = Picture.new(params[:picture])
respond_to do |format|
if #picture.save
format.html { redirect_to #picture, notice: 'Picture was successfully created.' }
format.json { render json: #picture, status: :created, location: #picture }
else
format.html { render action: "new" }
format.json { render json: #picture.errors, status: :unprocessable_entity }
end
end
end
There is an awesome gem for this purpose if you are using carrierwave
https://github.com/lebedev-yury/carrierwave-base64

Resources