I am using Rails 4.0.0 and Ruby 2.0.0
My app is for downloading mp3 files from s3 storage. Downloading a single mp3 track works, however downloading all selected tracks in current_cart is not working.
my controller is
tracks_controller.rb
def mp3_download_all
if !current_cart.line_items.blank?
current_cart.line_items.each_with_index do |track,i|
puts track.track.inspect
track.track.increment! :download_count
send_file(Paperclip.io_adapters.for(track.track.mp3_track_file).path, :type => track.track.mp3_track_file_content_type, :disposition => "attachment", :filename => track.track.mp3_track_file_file_name)
end
end
end
It's downloading only the last track.
Thanks
You can't really do this server side - one http request results in one response, i.e one file (to my knowledge browsers don't consistently support multipart mime responses).
Your best bet is probably to create a zip containing all the files and send that (the rubyzip gem makes this very easy). You might want to do this in some sort of background job (delayed_job, sidekiq etc)
Related
I created a small application that generates reports (mainly PDFs) from an sqlite3 database exported from GNUCash. I separated the app with a possibility of sharing it with other VFW posts that are currently using GNUCash or stuck with a paper general ledger. I call it a quasi API in that most reports can be generated in both HTML and PDF or it can just send data. Before I separated it, I had tried a multiple database approach, adding the sqlite3 database to my database.yaml along with my main postgresql database. It worked but that made the possibility of sharing difficult and I was a little skeptical about the approach.
All is fine with PDFs (prawn generated). I use RestClient to get the reports from the pdf server running on the same machine on a different port. For example, to generate a General Ledger this controller get action is called:
def ledger_pdf
resp = RestClient.get "http://localhost:8600/ledgers/#{params[:id]}/ledger_pdf"
send_data resp.body, filename: "Ledger#{params[:id]}",
type: "application/pdf",
disposition: "inline"
end
On the pdf server, this action responds
def ledger_pdf
pdf = Pdf::Ledger.new(view_context,params)
send_data pdf.render, filename: "Ledger#{params[:id]}",
type: "application/pdf",
disposition: "inline"
end
Getting the HTML version became a little more challenging! On the pdf server I have a one page menu that lists all the reports available, some by date and others with a collection route. This was for the 'sharing' version where someone only needs the reports and does not have another rails application that does other stuff. The server could be stand alone running on some box.
Rather then getting the data and creating a page on the main application from the data, I got the menu from the pdf server in an iframe tag. I though this was working great when I discovered that, in a view:
<iframe src="http://localhost:8600/ledgers/" width="100%" height="100%" id="rails_iframe">error!</iframe>
was calling localhost:8600 on my machine (which was running a development version and fooled me into thinking it was working!) rather than on the production server. After some researching I discovered that you can't do that. I guess the RestClient call to localhost:8600 is hidden from the browser, but the iframe call is not.
I guess I can get the HTML versions using the same RestClient approach and return text/html and use nokogiri to get the body/html and yield it somehow, if I knew how to do that.
The html.slim is really simple and I could just duplicate it on my main server after getting the data, but then I'd have two code bases to maintain.
Another approach would be to make it a real API and make it public so that the iframe approach would work, something I was trying to avoid (authentication and all that).
I guess my question is, does someone have another approach or can refine or give me pointers on my approaches.
You just need to add another action to your 'pdf' server which will render menu:
def menu
render json: {first: 'first/report', second: 'second/report'}
end
And then just fetch that menu from your main server (which is facing users):
def menu
resp = RestClient.get ...
# parse resp.body to get menu structure
# convert JSON response from pdf server to HTML with your ERB template
#menu_items = ...
end
Then just have route on your main server to pass information from collection server as well, something like:
get "reports/:collection/:date/:report", to: 'reports#fetch'
# then in your controller
def fetch
resp = RestClient.get "...#{params[:collection]}/#{params[:date]}/#{params[:report]}
...
end
Personally, I wouldn't use Rails server for pdf generation. I would've just used cron script which would run Ruby script for PDF generation, and then I would just upload them to S3 or some other cloud storage. My Rails server would've just return the link for that S3 file.
I want to create a zip archive on the fly while streaming it over the new Rails Live API. My problem at this point isn't the creation of the zip and sending it to the browser. It's more the problem to creating the zip on-the-fly for sending it over the rails response stream. The rubyzip documentation doesn't seem to be very well in some points for the streams.
here is the code that already works for creating and sending the file to our server (it doesn't use the ActionController Live API).
t = File.open("#{path}/#{zipfile_name}", "w")
Zip::OutputStream.open(t.path) do |zos|
# recursive method for building the zip structure in our system
stream_files(zos, params[:files], folder, "")
end
send_file t.path, :type => 'application/zip',
:disposition => 'attachment',
:filename => "#{zipfile_name}.zip"
t.close
Has anyone the same problem and got solved it already?
I need to have a system where a PDF is generated dynamically, asynchronously, and directly pushed to the browser, no disk storage is available. Getting resque to use prawn seems easy, its taking that data and sending it to the browser without storing it somewhere first, I can't find anything online. I thought about Faye, but can Faye handle pushing a PDF to the browser?
I've done this before in .net where i have an iframe's src attribute set to a service that returns a stream. The service aslo flips the http header to content-inline so that the browser doesn't try to download it but instead will try to render it inline. If you try to do this it wont work if the browser doesn't have a pdf plugin (must modern ones will but you always have that guy using IE6 yet) I don't know a lick of ruby but think you should be able to do something similar, or at least but an iframe on a page that targets a service written in something else.
u can use "PDFkit" for it.
the sample code is
def some_action
...
respond_to do |format|
format.html
format.pdf do
generate_pdf(file.html.haml, :css => [array of css file names that need to be added])
end
end
end
in application controller -
def generate_pdf(template, options={})
html = render_to_string(template, :layout => false)
kit = PDFKit.new(html, :orientation => 'Landscape')
kit.stylesheets << "#{Rails.root}/app/assets/stylesheets/default_css1.css"
kit.stylesheets << "#{Rails.root}/app/assets/stylesheets/default_css2.css"
# Add CSS
if options[:css]
options[:css].each do |css|
kit.stylesheets << "#{Rails.root}/app/assets/stylesheets/#{css}.css"
end
end
send_data(kit.to_pdf, :filename => 'latest.pdf', :type => 'application/pdf', :disposition => 'inline')
end
How big those PDFs are? Your database has BLOB columns (if you don't have storage, you are not using SQLite...) and you can store the resulting PDF in it.
Or you can store the resulting PDF in the Redis DB. Or save it in S3.
On the other end, the browser will be polling (with ajax) every now and then to know whether the PDF is complete, and as soon as it is ready it will download it and show it to the user.
I have paper_clip installed on my Rails 3 app, and can upload a file - wow that was fun and easy!
Challenge now is, allowing a user to upload multiple objects.
Whether it be clicking select fileS and being able to select more than one. Or clicking a more button and getting another file upload button.
I can't find any tutorials or gems to support this out of the box. Shocking I know...
Any suggestions or solutions. Seems like a common need?
Thanks
Okay, this is a complex one but it is doable. Here's how I got it to work.
On the client side I used http://github.com/valums/file-uploader, a javascript library which allows multiple file uploads with progress-bar and drag-and-drop support. It's well supported, highly configurable and the basic implementation is simple:
In the view:
<div id='file-uploader'><noscript><p>Please Enable JavaScript to use the file uploader</p></noscript></div>
In the js:
var uploader = new qq.FileUploader({
element: $('#file-uploader')[0],
action: 'files/upload',
onComplete: function(id, fileName, responseJSON){
// callback
}
});
When handed files, FileUploader posts them to the server as an XHR request where the POST body is the raw file data while the headers and filename are passed in the URL string (this is the only way to upload a file asyncronously via javascript).
This is where it gets complicated, since Paperclip has no idea what to do with these raw requests, you have to catch and convert them back to standard files (preferably before they hit your Rails app), so that Paperclip can work it's magic. This is done with some Rack Middleware which creates a new Tempfile (remember: Heroku is read only):
# Embarrassing note: This code was adapted from an example I found somewhere online
# if you recoginize any of it please let me know so I pass credit.
module Rack
class RawFileStubber
def initialize(app, path=/files\/upload/) # change for your route, careful.
#app, #path = app, path
end
def call(env)
if env["PATH_INFO"] =~ #path
convert_and_pass_on(env)
end
#app.call(env)
end
def convert_and_pass_on(env)
tempfile = env['rack.input'].to_tempfile
fake_file = {
:filename => env['HTTP_X_FILE_NAME'],
:type => content_type(env['HTTP_X_FILE_NAME']),
:tempfile => tempfile
}
env['rack.request.form_input'] = env['rack.input']
env['rack.request.form_hash'] ||= {}
env['rack.request.query_hash'] ||= {}
env['rack.request.form_hash']['file'] = fake_file
env['rack.request.query_hash']['file'] = fake_file
if query_params = env['HTTP_X_QUERY_PARAMS']
require 'json'
params = JSON.parse(query_params)
env['rack.request.form_hash'].merge!(params)
env['rack.request.query_hash'].merge!(params)
end
end
def content_type(filename)
case type = (filename.to_s.match(/\.(\w+)$/)[1] rescue "octet-stream").downcase
when %r"jp(e|g|eg)" then "image/jpeg"
when %r"tiff?" then "image/tiff"
when %r"png", "gif", "bmp" then "image/#{type}"
when "txt" then "text/plain"
when %r"html?" then "text/html"
when "js" then "application/js"
when "csv", "xml", "css" then "text/#{type}"
else 'application/octet-stream'
end
end
end
end
Later, in application.rb:
config.middleware.use 'Rack::RawFileStubber'
Then in the controller:
def upload
#foo = modelWithPaperclip.create({ :img => params[:file] })
end
This works reliably, though it can be a slow process when uploading a lot of files simultaneously.
DISCLAIMER
This was implemented for a project with a single, known & trusted back-end user. It almost certainly has some serious performance implications for a high traffic Heroku app and I have not fire tested it for security. That said, it definitely works.
The method Ryan Bigg recommends is here:
https://github.com/rails3book/ticketee/commit/cd8b466e2ee86733e9b26c6c9015d4b811d88169
https://github.com/rails3book/ticketee/commit/982ddf6241a78a9e6547e16af29086627d9e72d2
The file-uploader recommendation by Daniel Mendel is really great. It's a seriously awesome user experience, like Gmail drag-and-drop uploads. Someone wrote a blog post about how to wire it up with a rails app using the rack-raw-upload middleware, if you're interested in an up-to-date middleware component.
http://pogodan.com/blog/2011/03/28/rails-html5-drag-drop-multi-file-upload
https://github.com/newbamboo/rack-raw-upload
http://marc-bowes.com/2011/08/17/drag-n-drop-upload.html
There's also another plugin that's been updated more recently which may be useful
jQuery-File-Upload
Rails setup instructions
Rails setup instructions for multiples
And another one (Included for completeness. I haven't investigated this one.)
PlUpload
plupload-rails3
These questions are highly related
Drag-and-drop file upload in Google Chrome/Chromium and Safari?
jQuery Upload Progress and AJAX file upload
I cover this in Rails 3 in Action's Chapter 8. I don't cover uploading to S3 or resizing images however.
Recommending you buy it based solely on it fixing this one problem may sound a little biased, but I can just about guarantee you that it'll answer other questions you have down the line. It has a Behaviour Driven Development approach as one of the main themes, introducing you to Rails features during the development of an application. This shows you not only how you can build an application, but also make it maintainable.
As for the resizing of images after they've been uploaded, Paperclip's got pretty good documentation on that. I'd recommend having a read and then asking another question on SO if you don't understand any of the options / methods.
And as for S3 uploading, you can do this:
has_attached_file :photo, :styles => { ... }, :storage => :s3
You'd need to configure Paperclip::Storage::S3 with your S3 details to set it up, and again Paperclip's got some pretty awesome documentation for this.
Good luck!
I am using this Spreadsheet gem to export xls file.
I have the following codes in my controller:
def export
#data = Data.all
book = Spreadsheet::Workbook.new
sheet = book.create_worksheet :name => "data"
contruct_body(sheet, #data)
book.write "data.xls"
end
In this way, I can fill in the data and save it in the root directory.
But I want to download it instead of save it. How could I modify the code so that the user prompted to select his local directory to save the file? (better if without saving a copy in the server side)
Please help!
You can send it to the browser without saving it as a local file at all as follows
spreadsheet = StringIO.new
book.write spreadsheet
send_data spreadsheet.string, :filename => "yourfile.xls", :type => "application/vnd.ms-excel"
You could try this code
book.write "data.xls"
send_file "/path/to/data.xls", :type => "application/vnd.ms-excel", :filename => "data.xls", :stream => false
# and then delete the file
File.delete("path/to/data.xls")
Passing :stream => false to send_file will instruct Rails to copy the entire file into memory before streaming, so using File.delete immediately after send_file would be fine since send_file returns immediately without waiting for the download to complete. Having said that, with very large files you may see some memory bottle necks depending on the amount of memory available.
HTH
I understand this is insanely old, but I was looking for it so someone else might be.
This is the answer. (I'm using Sinatra.)
https://github.com/zdavatz/spreadsheet/issues/125#issuecomment-370157753
The case happen on mybody.
I used the ajax request by remote::true to export excel file, nothing display on browser without any error message on console.
Delete the remote params from the form, it works well.