Shizzle

My little notebook

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.

Filed under: Mixed

Leave a Reply

Your email address will not be published.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre lang="" line="" escaped="" highlight="">