File Uploads with Ruby on Rails
I thought I would do a quick post about file uploads using Rails, since I recently had to create an application that would allow users to upload image files. At first, I was storing the images as binary data in the MySQL database, which is what a couple of the books that I have recommended. However, I quickly realized that this is grossly inefficient, and not terribly scalable compared to storing the image files in the filesystem. It’s something like 100x faster to grab an image from the disk on the server than it is to access the database and grab that same binary data from a blob field. Additionally, I noticed that the data didn’t always transfer right when I was migrating database information.
So I decided to store the files on the file system because it is faster. More importantly, the subject leads me to discuss an interesting feature of the Ruby programming language, which to me is a huge time saver and a big plus. Not only that, but I think it illustrates how Ruby is a truly object-oriented language, as opposed to PHP which (to me) does not do a terribly good job at being “object oriented”. That being said, I’ll start by saying that everything in Ruby is an object. To that effect, each object has a set up attributes, which can be read back and assigned. However, it’s interesting how the language deals with assigning attributes to objects. Hopefully I’m using all the right terminology here, but I’ll use an example of my file uploads to illustrate.
So I have a model that wraps a database table. In that table are fields for the uploaded files MIME type, the name of the file, and the path to the file on the filesystem. However, when I upload data from the form, I don’t have these fields in my form, but rather a file upload field called “media”. The question then becomes how do those fields get populated in the database. The answer is that you can tell a Ruby object what to do when it is assigned a value. What? Let’s a take a look at the media object, which is created when the model receives the uploaded data.
By assigning an attribute accessor called media in my model using the following code:
def media=(data)
end
I can tell Ruby to do custom things when a data is assigned to the media object. In the case of the file uploads, when the media object is assigned, the model them goes to work by assigning the MIME type of the uploaded file, the name of the file, and the path to the file. These values are all stored in the database accordingly by assigning them to the appropriate columns in the model. To go a step forward, and because all data related logic should reside in your models, I also include the upload code to actually upload and store the file.
Remember that when the file is uploaded, it is assigned to an accessor called “media”, which contains the file data. Once the appropriate data has been extracted and stored in the database, the actual file itself is stored to the file system. Take a look at at the final code below to see what I mean:
def media=(data)
if data != “”
name = base_part_of(data.original_filename)
directory = “public/uploads/” + Time.now.to_s(:file)
path = File.join(directory, name)
Dir.mkdir(directory) unless File.directory?(directory)
File.open(path, “wb”) { |f| f.write(data.read) }
self.name = name
self.mime = data.content_type.chomp
self.path = path.gsub(/public/, ‘’)
end
end
As you can see, that code will be executed any time a value is assigned to the object called “media”. In this case, the first thing that happens is that if there is no uploaded data, nothing will occur. Once it has verified that there is data to work with the file name is extracted. We also assign a directory to hold the uploaded file in, which is based on the current time to avoid ever overwriting previously uploaded images. If that directory does not exist, it will create one. From there, the path is created, telling us how to get at the file once it lives on the filesystem. Finally, we write the data to the disk in the proper directory.
Once all of the upload work is done, the model gets assigned a name, mime, and path to be put into the database. That way the image lives on the filesystem and the data about the image is stored in the appropriate row of the database. When the model is saved, it will automatically assign the name, mime, and path to the database.
Hopefully this will help out some for those trying to understand file uploads. The idea of being able to tell Ruby to execute some tasks whenever it assigns a value to an object to me is fantastic. It makes for much leaner, cleaner code, as well as helps to simplify things quite a bit. I can see why DHH chose Ruby over another language.
NOTE: In the example above, I use a custom function called
base_part_of()to strip out the file name. Additionally, you’ll notice that I use a custom Time format as well called:file. Custom time formats need to be defined in theenvironments.rbfile of a Rails application, and don’t forget to restart the app so that it will work.

Connect with us