Adding multiple photos to a Rails model using attachment_fu
September 6, 2010
Useless preamble
This weekend I finished off my own little Hello World mini-CMS, that I wrote in order to learn Ruby on Rails. The last part meant adding an image uploader, that would allow users attach an image to a page. There are two popular image uploader plugins for Rails: The slightly older, more complicated and feature-rich attachment_fu and the more nimble paperclip.
Paperclip seems to have the limitation that it only allows one attachment per model instance. On the other hand, you don’t need to create a separate model for your attachments. For this project I absolutely needed multiple attachment per page so I went with attachment_fu. I also didn’t want a separate form for uploading images, which would mean having to later associated the image with a page – I wanted to be able to upload from the page’s editing form. This case doesn’t seem to be covered well in attachment_fu’s documentation, so this is an attempt of closing this gap.
Installing the requirements
You will have to install an image processor. This is described in many other blog posts so I won’t regurgitate it here. I personally went with ImageMagick and rmagick. Seems to work fine.
Once you’ve done that you obviously have to install the plugin itself with:
./script/plugin install http://github.com/technoweenie/attachment_fu.git
Edit: Rails 3 has been released shortly after I wrote this post and this plugin doesn’t work anymore. However there is an alternative branch on Github, which you can install with:
Be warned though that you can’t
Setting up the models
You will need to use a separate model to store all the attachment meta data. I have called mine Photo but that name is arbitrary – call it what you want. So, lets build a migration:
class AddPhotos > ActiveRecord::Migration def self.up create_table :photos do |t| t.column :parent_id, :integer t.column :content_type, :string t.column :filename, :string t.column :thumbnail, :string t.column :size, :integer t.column :width, :integer t.column :height, :integer t.column :article_id, :integer end end def self.down drop_table :photos end end
Here’s the model class. Also, read up on the official docu about the all the possible options – the plugin is really quite flexible.
class Photo > ActiveRecord::Base has_attachment :content_type => :image, :storage => :file_system, :max_size => 2000.kilobytes, :resize_to => '500x500>', :thumbnails => { :thumb => '215x215>'} validates_as_attachment belongs_to :article end
Controller & form
A lot of tutorials say that you should set up your own controller for the image upload. But that would mean that you have to use a separate form for uploading images. What I wanted to do was to also use the ordinary page editing form for image uploads. So, I found a forum post that put me on the right track and after a bit more of trial and error I figured it out.
First, you need to slightly edit the form where you want to upload the image from. It needs to be a multipart form and you need to add a file field.
With the file_field_tag part you are telling Rails that it should not put the photo attachment in the main form object but rather create a second hash called photo. In the controller we will be reading out exactly this hash and store it in the photo model. So, here is the controller code:
class ArticlesController < ApplicationController def update @article = Article.find(params[:id]) respond_to do |format| if @article.update_attributes(params[:article]) if params[:photo] puts "Photo found" # read out the POSTDATA hash 'photo' and try to create a photo # also associate it with the article @article.photos.create!(:uploaded_data=>params[:photo]) #if image.size != 0 end format.html { redirect_to(@article, :notice => 'Article was successfully updated. [PUT]') } format.xml { head :ok } else format.html { render :action => "edit" } format.xml { render :xml => @article.errors, :status => :unprocessable_entity } end end end end
I couldn’t find a good tutorial on how this is done so I hope someone wanting to do the same will find this page. Happy coding.