imjp
imjp

Reputation: 6695

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

Upvotes: 3

Views: 12008

Answers (5)

Bangline
Bangline

Reputation: 890

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

Upvotes: 9

flapjack
flapjack

Reputation: 724

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

Upvotes: 0

Thong Kuah
Thong Kuah

Reputation: 3283

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 :

  1. Create a method in your model to generate the text file contents. Let's say this method is called list_data

  2. 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

Upvotes: 3

dbenhur
dbenhur

Reputation: 20398

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.

Upvotes: 3

James
James

Reputation: 2293

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

Upvotes: 1

Related Questions