Uploading files to Rails models
Tuesday, August 29th, 2006Riassunto: ecco come aggiungere un’immagine a un modello in una applicazione Rails
Just a summary of what it takes to add an image to a model in a Rails application. This is to remind myself of the steps it took to make it work.
There are two plugins out there: file_column and acts_as_attachment. The second is more full-featured, and requires a separate table for each attachment. So I chose to use the former, for simplicity. I can always change my mind later.
Using file_column is straightforward, unless you wish to change the default place where image files are saved; in which case you must add options that are not well documented. The default location is not good, because it chooses a different directory for every model: public/people, public/places, public/foobar, and so on. This is not good when deploying with capistrano, because you will need to make all of these dirs symbolic links. Much better to have a single root for all uploads: public/uploads/people, public/uploads/places, …
Suppose you want to add an “image” file attachment to a model “Foobar”. First you must add an “image” column to the foobars table:
$ script/generate migration AddImageColumnToFoobars
...
edit the migration file
class AddColumnImageToFoobars < ActiveRecord::Migration
def self.up
add_column :foobars, :image, :string
end
def self.down
remove_column :foobars, :image
end
end
and execute the migration
$ rake migrate
...
Time to do a little functional test. Open up test/functional/foobar_controller_test.rb and write something like
def test_update_image
post :edit, :id => 2, :foobar => {
:image => upload(Test::Unit::TestCase.fixture_path
+ '/files/animal.jpg', 'image/jpg'),
}
assert_redirected_to :action => 'page', :name => old_content.name
updated_foobar = Content.find(2)
assert_not_nil updated_foobar.image, "what? no image?"
assert_equal '2/animal.jpg', updated_foobar.image_relative_path
end
Run the test. Does it fail? Good! Failure is progress.
Now modify the Foobar model
class Foobar < ActiveRecord::Base
file_column :image,
:root_path => File.join(RAILS_ROOT, "public", "upload"),
:web_root => "upload/"
end
The :root_path option says we want to save all uploads beneath the “public/uploads” directory. The :web_root is needed so that the proper url for the pictures can be computed. A bit redundant, but I see how this can be handy for apps that are deployed on multiple servers.
Try the functional test again. It should work now.
Now modify the views/foobar/_form.html adding
<%= file_column_field "foobar", "image" %>
You must also modify the form tag in views/foobar/edit.rhtml and new.rhtml adding multipart encoding, this way:
<%= form_tag({:action => 'update', :id => @foobar},
{:multipart => true}) %>
otherwise, upload will not work.
At last, you can show the image in your Foobar pages:
<%= image_tag url_for_file_column(@foobar, "image") if @foobar.image %>
The “if” part is needed in case no image has been uploaded yet.
The finishing touch is to add validations to the model:
class Foobar < ActiveRecord::Base
file_column :image,
:root_path => File.join(RAILS_ROOT, "public", "upload"),
:web_root => "upload/"
validates_file_format_of :image, :in => ["gif", "png", "jpg"]
validates_filesize_of :image, :in => 15.kilobytes..1.megabyte
validates_image_size :image, :min => "1200x1800"
end
Conclusion
The “file_column” plugin is simple and it works beautifully, even if the docs could be better. Be sure to consult the rdoc within the source files.
If you need to run searches on your uploaded files, such as, for instance, looking for all pictures in “landscape” format, then file_column does not help. The other solution, acts_as_attachment could be the right choice.