1.2.1. The simplest thing that could possibly work
1.2 Solution
Have the controller read the uploaded file and save it’s content to the file system. This can be done using File.open, #read and File.write.
Example
Here is a UserController that takes an uploaded file and saves it to the /public/pictures/ directory.
class UserController < ApplicationController
...
def save_picture
@user = User.find(@params['user']['id'])
File.open("#{RAILS_ROOT}/public/pictures/#{@user.id}.jpg", "wb") do |f|
f.write(@params['picture_file'].read)
end
redirect_to :action => 'show', :id => @user.id
end
...
end
1.3 Discussion
Uploaded files are handled much like regular form parameters, except that they behave like IO objects. Before we get into the details of how to read and save an uploaded file, it’s worth first examining what kind of object the uploaded file actually is.
About the uploaded file object
While the uploaded file is a special beast,it is not difficult to use. The object rails receives are processed by a CGI object. The object that CGI returns to Rails isn’t actually a file object, but it’s close enough for our purposes.
Details from the CGI class documentation
... the value is not a string, but an IO object, either an IOString for small files, or a Tempfile for larger ones. This object also has the additional singleton methods: local_path(): the path of the uploaded file on the local filesystem original_filename(): the name of the file on the client computer content_type(): the content type of the file
Accessing our uploaded file
The uploaded file is just another parameter available in the @params hash. The key to the hash is the name of the file field from the form.
@params['picture_file']
Read Me
Because uploaded files behave like IO objects, we can use their #read method to read its contents.
Block it out
File.open can take a block as it’s argument. Doing so means that Ruby takes care of closing and releasing the file once the block completes.
File.open(destination, "w") do |f|
f.write(@params['picture_file'].read)
end
Note for Windows: to avoid corrupting binary files, you must call File.open in binary mode. Change the “w” flag to “wb”, like this:
File.open(destination, "wb") { |f| f.write(uploaded.read) }
Not very good
The given solutions is crude at best. It is an illustrative example only. There are a number of significant limitations:- it assumes all images are JPG.
- the paths are hard-codded in the controller
- no validation is done on filesize
- it doesn’t include the image into the domain language. The user object doesn’t know* how to do anything with the image, or if it even has an image..