Basic File Uploads With Rails
Jul 25, 2006There are times in your Rails applications when you may want to be able to upload files to make them available from within the application. One way to do this, as shown in the Agile Web Development with Rails book, is to store them in the database. I’m going to show you how to store them on the file system.
The Upload Form
First thing do is create the form for uploading files.
<%= start_form_tag({:action => 'new_upload'}, :multipart => true) %>
Description:<br />
<%= text_field('upload', 'description', :size => '20') %><br />
Choose File:<br />
<%= file_field('upload', 'data', :size => '20') %><br />
<%= submit_tag('Upload') %>
<%= end_form_tag %>
The important things to note here is that I’ve added
:multipart => trueto the form tag. This allows our POST action to properly pass along the binary data from the file field.
The Controller Action
So now we have a way to send a file for upload to the application. We need to be able to accept that data and process it. The action is called “new_upload”, and you’ll notice that I have a request.post? condition in there. As mentioned in Combining Form Actions this saves us the effort of needing separate “new” and “create” actions. Here’s the controller action:
def new_upload
@upload = Upload.new
if request.post?
path = "/files/" + params[:upload][:data].original_filename
root = "#{RAILS_ROOT}/public"
#prevent overwriting
if Dir[root+path].length > 0
flash[:notice] = "File already exists!"
redirect_to :action => "new_upload"
return
end
#I'll explain these two lines of code next
data = params[:upload][:data].read
upload(data, root+path)
@upload.description = params[:upload][:description]
@upload.path = path
if @upload.save
flash[:notice] = "File was successfully uploaded."
else
flash[:notice] = "Upload error!"
end
redirect_to :action => 'uploads'
end
end
Now you’re not required to do this, but I have an actual Upload model to go along with the binary data, this way I can store the list of what files have been uploaded in the database. The “uploads” action is a listing of the uploaded files with delete links to remove the files if necessary.
Storing The Data
So what’s with these two lines?
data = params[:upload][:data].read upload(data, root+path)
First, the “read” method reads the actual binary data of the file we uploaded. We still need to save this file to the file system. That’s where the “upload” method comes in.
def upload(data, path)
File.open(path, 'w') do |f|
f.write(data)
end
end
The method will write the file we uploaded to the path we specifed on the file system. If you curious about what f.write does you can find all the specifics in the Ruby docs.
Final Notes
So now we have a reasonably elegant way to handle file uploads. It’s not completely bulletproof, but it will get the job done the vast majority of the time.
One caveat here is that this way of uploading files doesn’t deal particularly well with large files. If the file takes too long to upload it will just time out. I’m still investigating an elegant way of handling large file uploads, as it’s a common problem across the web.