Uploading files to Rails models

Riassunto: 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.

5 Responses to “Uploading files to Rails models”

  1. Bruno Says:

    I am trying to upload multiple images at once using file column? Do you know how to do that?

  2. matteo Says:

    I didn’t try, but I suppose it should be possible. If you need to associate an unlimited number of images to a model, I would create a separate model, such as Picture, and then set up a one-to-many relationship. Email me if you want to discuss this further.

  3. RED, GREEN, REFACTOR IT! » Blog Archive » Red, Green, Refactor It! Says:

    […] Il colore del tema non poteva che essere il rosso: failure is progress!. […]

  4. Anton Says:

    Where does the upload() method come from in the functional test? It didn’t work for me. Thanks!

  5. matteo Says:

    It’s a function defined in vendor/plugins/file_column/lib/test_case.rb

    Good luck

    Matteo

Leave a Reply