The great thing about frameworks like Rails (in my opinion) is that the large number of people doing many of the same tasks results in some really great reusable pieces of code that make common complex features almost trivial to implement. This was brought right to the forefront of my attention again today when I started working on a new feature for our medicaid billing system.
The story is this: We have a physician who writes prescriptions for students who need them. It's better if we don't have to mail him all the documentation for every student he's writing a script for. The best conceived solution was to have school districts upload their docs to our website (associated with a student), and let the physician download them on his side.
Now, surely I'm not the first person to ever want to have uploaded files associated with an entity (that's basically facebook's whole business model, after all).
Sure enough,
attachment_fu to the rescue!
This was dead simple. After installing the plugin (ruby script/plugin install git://github.com/technoweenie/attachment_fu.git), I dug into the docs and started coding.
First I created a new model to house the necessary file for the student (it's called an "IEP", for Individual Education Plan, so don't get thrown by the acronym):
class Iep < ActiveRecord::Base
belongs_to :student
has_attachment :content_type => "application/pdf",
:storage => :file_system,
:path_prefix => "public/iep",
:max_size => 1000.kilobytes
validates_as_attachment
end
Small and simple, right? Here's what it means:
the "belongs_to :student" is the standard ActiveRecord association, indicating that a student entity is the parent of this object (and also implying that the "IEP" object has a foreign key called student_id).
"has_attachment" is the method that configures the file you're going to upload. :content_type, obviously, is the type of file you expect. In my case (and yours if google brought you to this entry) that type is "application/pdf", and if anything else comes in it won't be accepted.
":storage" is how you tell attachment_fu where you want to store these files. Other options besides the filesystem are the database and amazon_s3, but those are both beyond the scope of this article. I just wanted to get these items onto the disk, and then back off on another computer, and the filesystem was the way to go that required the least configuration.
":path_prefix" is the file path you want prepended to the storage location for all files stored by attachment_fu. In my case, "public/iep" is just a directory on my local filesystem, but in production it's a symlink to a location outside of the application folder so that the uploaded files will persist across new deployments (handy strategy for anyone who needs to upload files, thanks to EngineYard for handling that for me).
":max_size" should be self-explanatory. If it isn't, you may be in the wrong job.
Next I set up a couple controller actions to handle the actual uploading of the file:
def upload_iep
@student = Student.find(params[:id])
@iep = Iep.new
end
def exec_upload_iep
@student = Student.find(params[:id])
if @student.iep.nil?
@iep = Iep.new(params[:iep])
@iep.student_id = @student.id
else
@iep = @student.iep
@iep.update_attributes(params[:iep])
end
if @iep.save
flash[:notice] = 'IEP Uploaded!'
redirect_to :action => :all_students
else
redirect_to :action => :upload_iep,
:id => @iep.student_id
end
end
It ain't restful, but it works. One action to load the form, the other to accept the actual upload. Nothing in there should look different than dealing with regular rails forms. Next we have the view:
<% form_for(:iep, :url => {:action=>:exec_upload_iep},
:html => { :multipart => true }) do |f| -%>
<p>
<label for="iep">Upload An IEP:</label>
<%= f.file_field :uploaded_data %>
<%= hidden_field_tag :id,@student.id%>
</p>
<p><%= submit_tag 'Upload' %></p>
<% end -%>
The only important bits to remember here are the ":multipart => true" which makes sure that the form can accept file uploads, and the ":uploaded_data" attribute which is what attachment_fu searches for when manipulating attached files. Now it's easy to upload an IEP attached with a student anytime. And the best part is, retrieval is a snap:
<td>
<a href='#{student.iep.public_filename}'>Download IEP</a>
</td>
that "public_filename" method returns the necessary path to retrieve the file, making it easy for the physician to download the IEP on his computer. Done and done. Thanks to
technoweenie for writing and maintaining this plugin for the community!