Rails send_data creating blank file from controller - ruby-on-rails

I am trying to download a file filled with a string. However, no matter which way I try, the file ends up blank.
Here is the relevant code:
def export(notes)
stream = render_to_string(:template=>"cards/export.enex.erb", :locals => {:notes => notes}, :formats => [:enex])
send_data(stream.to_s, :filename => "notes.enex")
end
I have been using Rails.logger.info to try to track down the problem and have confirmed that stream is not empty and (when prompted to) my log shows that the file was sent full of the correct data. I am using a custom mime type (enex) and that is all set up correctly in config. I've tried several different methods and nothing works. Here are some other attempts:
(1)
#notes = notes
render file: "cards/export", formats: [:enex], type: 'text/plain', disposition: 'attatchment; filename=cards.enex'
(2)
render template: "cards/export", formats: [:enex], :locals => {:notes => notes}, type: 'text/plain', disposition: "attatchment", filename: "notes.enex"
(3)
send_file 'app/views/cards/export.enex.erb', type: 'application/enex', disposition: "attachment; filename=notecards.enex", :x_sendfile=>true
In each case, the file ends up blank.
As you can see, the string I am using is created by filling out an erb form. If it matters, "notes" is a hash that I use to fill out the form. I know how to get this to work by using a button on a view and a respond_to in the controller but I am purposely not using the database and would prefer to solve the problem using a private controller method as shown.
I am using Rails 4
Can you see anything that would cause the send_data to fail?

Try using send_file but instead of giving it the path to the erb, give it the path to the file you generated with that template and specify the type as application/xml or text/xml. I don't have experience with enex files, but I think this may simplify the issue.
file = "my_file.enex"
File.open(file, "w") do |f|
f << render_to_string(:template=>"cards/export.enex.erb", :locals => {:notes => notes}, :formats => [:enex])
end
send_file(file.path, type: "application/xml")

Related

Invalid/damaged download using send_data with PowerPoint

I am generating a PowerPoint using the powerpoint gem and up until now I was using send_file, but now I want to use send_data. The reason for this is that send_data blocks the application from doing anything else until the call completes, while send_file allows the app to continue executing while the file downloads, which sometimes, due to coincidental timing, leads to the file being deleted before the user tells their browser to start the download and the web server finishes sending the file and thus a blank or incomplete file is downloaded.
This is how I create my PowerPoint:
#Creating the PowerPoint
#deck = Powerpoint::Presentation.new
# Creating an introduction slide:
title = 'PowerPoint'
subtitle = "created by #{username}"
#deck.add_intro title, subtitle
blah blah logic to generate slides
# Saving the pptx file.
pptname = "#{username}_powerpoint.pptx"
#deck.save(Rails.root.join('tmp',pptname))
Now, on to what I have tried. My first instinct was to call as follows:
File.open(Rails.root.join('tmp',"#{pptname}"), 'r') do |f|
send_data f.read, :filename => filename, :type => "application/vnd.ms-powerpoint", :disposition => "attachment"
end
I've also tried sending the #deck directly:
send_data #deck, :filename => pptname, :disposition => "attachment", :type => "application/vnd.ms-powerpoint"
Reading it ends with a larger file than expected and it isn't even able to be opened and sending the #deck results in a PowerPoint with one slide that simply has the title: #Powerpoint::Presentation:0x64f0dd0 (aka the powerpoint object).
Not sure how to get the object or file into binary format that send_data is looking for.
You need to open the file as binary:
path = #deck.save(Rails.root.join('tmp',pptname))
File.open(path, 'r', binmode: true) do |f|
send_data f.read, filename: filename,
type: "application/vnd.ms-powerpoint",
disposition: "attachment"
end
This can also be done by calling the #binmode method on instances of the file class.
send_data #deck, ...
Will not work since it calls to_s on the object. Since Powerpoint::Presentation does not implement a #to_s method you're getting the default Object#to_s.

Rails download http response/displayed site

Instead of displaying the xml file rendered by the index.api.rsb file in my browser, i want to download it. To me this sounds very simple, but I cant find a solution.
I tried the following in the controller-method:
def split
if params[:export] == "yes"
send_file *here comes the path to xml view*, :filename => "filename", :type => :xml
end
respond_to ...
end
The result is a MissingFile exception...
Thanks in advance
Note that :disposition for send_file defaults to 'attachment', so that shouldn't be a problem.
If you have a MissingFile exception, that means the path is incorrect. send_file expects the path to an actual file, not a view that needs to be rendered.
For your case, render_to_string might be what you need. Refer to this related question. It renders the view and returns a string instead of setting the response body.
def split
if params[:export] == "yes"
send_data(render_to_string path_to_view, filename: "object.xml", type: :xml)
end
end
To force it to download it, add :disposition => attachment to your send_file method.
Source: Force a link to download an MP3 rather than play it?

Sending an image through JSON data

noobie here hope you guys don't mind! Im trying to query my user/id/pictures.json but all it returns are attributes cus i did a generic format.json {render :json => #photo.to_json()}. My question is how can i create and encapsulate the actual data from the images, so my client can turn that data in to an image? And also what do i need to create(attribute wise) besides the path of image(say you only had useless attributes eg: height content_type, description, thumbnail file_name)?
this is what im trying in my index.json.erb so far
}
<% #photos.each do |photo|%>
data: <%= StringIO.new(Base64.encode64(photo.public_filename(:large))) %>
<%end%>
}
i am getting back
{
data: #<StringIO:0x1058a6cd0>
}
which is not the IMGdata im looking for
looking for
Have a look at Data-URIs.
They essentially are Base64-encoded entities (documents) formatted as a URI
[{ "name":"red dot", "data": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=="}, ...]
[UPDATE]
You need to read the file and encode it as Base64 (you also need to strip the newlines away in rails 2.3.x)
data = ActiveSupport::Base64.encode64(File.read("/images/image1.png")).gsub("\n", '')
uri = "data:image/png;base64,#{data}"
I think you are using Ruby on Rails, aren't you?
Then there are some steps needed to download an image (e.g. a png):
Create a mime type
Go to config/initializers/mime_types.rb and insert Mime::Type.register "image/png", :png at the end.
Create an image
For example, you could use the gem Chunky_PNG to create an image, see at http://rubygems.org/gems/chunky_png and https://github.com/wvanbergen/chunky_png/wiki
Prepare your controller
You have to tell your controller, that it can accept pngs. Modify your controller the following way
class UsersController < ApplicationController
respond_to :json, :png
def show
# your own stuff
# ...
respond_with(response) do |format|
format.json
format.png do
send_data ChunkyPNG::Image.new(width, height, ChunkyPNG::Color::TRANSPARENT), :type =>"image/png", :disposition => 'inline'
end
end
end
end
This will create a fully transparent image. If you want to draw something in this, look at the Chunky PNG docs.
It's up to the client how to render it really. This works for me, maybe worth a try.
render json: #thumbnail, type: :jpeg, content_type: 'image/jpeg'

How do I create a temp file and write to it then allow users to download it?

I'm working on my first application and I need some help with allowing my users to download a text file with certain variables that are being displayed on the page.
Take a shopping list for example.
Let's say you allow your users to create a shopping list of products, and then display the shopping list with the items on a shopping list page,
e.g. localhost:3000/list/my-list
Take a look at the example code below (which is probably incorrect):
File.open('shopping_list.txt', 'w') do |file|
file.puts 'Item 1: #{product_1.name}'
file.puts 'Item 2: #{product_2.name}'
file.puts 'Item 3: #{product_3.name}'
end
Which then creates a text file that has the following content:
Item 1: Eggs
Item 2: Butter
Item 3: Bread
Users should then be able to download this file (i don't want this file to be stored on the server) via a download link.
I have no idea how to achieve this, but I'm hoping you guys can guide me. :D
TL;DR
create text files populated with model data (perhaps create a method to achieve this?)
text files should not be stored on the server, but created as users click the download button (not sure if this is the rails way but perhaps someone could show me a better way)
I am assuming there is a resource for List with the attribute name as the name of the list and a list has_many Item which has an attribute description
First off, create a download path change your routes config/routes.rb
resources :lists do
member {get "download"}
end
Now if you run a rake routes in the console you should see a route like
/lists/:id/download
Whats more you should now have the helpers download_list_url & download_list_path to use in your view like
<ul>
<% #lists.each do |list| %>
<li> <%= list.name %> - <%= link_to 'Download List', download_list_path(list) %> </li>
<% end %>
</ul>
In your lists_controller add the action, and as you dont actually want to keep the file on the server disk just stream the data as a string
def download
list = List.find(params[:id])
send_data list.as_file,
:filename => "#{list.name}.txt",
:type => "text/plain"
end
Finally you see I have used a as_file method which you should add to the model (I prefer not to do this stuff in controllers, fat models, skinny controllers). So in the List model
def as_file
output = [self.name]
self.items.each {|item| output << item.description }
output.join("\n")
end
You say you don't want to store the file on the server, but "download" it on request; this sounds like you just want to generate and deliver a text document in response to the download link. There are several approaches, but you want to be sure of setting the mime-type so the browser sees it as a text file instead of an html document.
product_info = [
"Item 1: #{product_1.name}",
"Item 2: #{product_2.name}",
"Item 3: #{product_3.name}",
].join("\n")
render :text => product_info # implies :content_type => Mime::Type["text/plain"]
BTW, your example with open/puts above won't output what you think since single-quoted strings don't interpolate.
so, you wish to :
create text files populated with model data (perhaps create a method
to achieve this?)
text files should not be stored on the server, but
created as users click the download button (not sure if this is the
rails way but perhaps someone could show me a better way)
You have the right idea, here's what to do :
Create a method in your model to generate the text file contents. Let's say this method is called list_data
It seems like you have an existing controller action called my_list. Hence we can call our new method in the controller like so :
.
def my_list
# pre-existing code
respond_to do |format|
format.html # show html page as before
format.text do
send_data #list.list_data, :content_type => 'text/plain', :filename => 'my-shopping-list.txt'
end
end
end
To link to the download, just use link_to :action => my_list, :format => 'text'
See http://api.rubyonrails.org/classes/ActionController/DataStreaming.html#method-i-send_data for full docs on send_data
Caveat & explanations : Using the method above, there isn't really an explicit creation of files, Rails is streaming it for you. Hence this method is not suitable for very large files, or when the generation of the file content will take a while. Use a delayed method to generate the file and store it - the file contents somewhere if that's the case - but we can use send_data once it has been generated
You could try a combination of TempFile and send_file. In your controller action ..
file = Tempfile.new('foo')
file.write("hello world")
file.close
send_file file.path
At Rails 2.3 you can use Template Streaming. Working with Redmine I can remember something like that, you have to adapt for your case. Reference: Streaming and file downloads
require "prawn"
class ClientsController < ApplicationController
# Generate a PDF document with information on the client and return it.
# The user will get the PDF as a file download.
def download_pdf
client = Client.find(params[:id])
send_data(generate_pdf, :filename => "#{client.name}.pdf", :type => "application/pdf")
end
private
def generate_pdf(client)
Prawn::Document.new do
text client.name, :align => :center
text "Address: #{client.address}"
text "Email: #{client.email}"
end.render
end
end
Using the Thong Kuah you must just change the "content_type" param:
def my_list
# pre-existing code
respond_to do |format|
format.html # show html page as before
format.text do
send_data #list.list_data, :content_type => 'text/plain', :filename => 'my-shopping-list.txt'
end
end
end

Saving XML files with Rails

im working on a Rails project that should create XMl files, or to be more specific
use existing XMl templates and put content from the database in it.
So i dont need to create the xml structure, basically just rendering a template with content.
What would be the smartest way to do that?
So far i have a file.xml.erb in my layout folder
and i have a custom route "/renderXML" that does
def renderXML
#reading_question = ReadingQuestion.find(params[:id])
render :file => 'layouts/question.xml'
end
This works, but i also want to save the file, not only show it (actually viewing it is not really needed).
For saving i found this
File.open('fixed.xml','w'){|f| f.write builder.to_xml}
How do i access the rendered file and save it with some method like above?
Perhaps something like:
s = render_to_string :file => 'layouts/question.xml'
File.open('fixed.xml','w'){|f| f.write s}
render :text => s
Another approach :
send_data fixed, :type => 'text/xml; charset=UTF-8;', :disposition =>
"attachment; filename=fixed.xml"

Resources