Paperclip, Multiple Image Uploads, Rails 2.3.4

Hello folks,

It’s been a while since I updated the blog and rails has changed a bit since then. I thought I’d revise one of my previous posts: the one about uploading multiple images using paperclip. This time we’re going to be doing it using Rails 2.3.4. I’m going to be using the paperclip gem, as well as the nifty_generator gem(for ease of demonstration). Just google them if you don’t know what they are or if you’re new to the rails world ;)

Lets start off by installing the necessary gems.

gem sources -a http://gems.github.com
sudo gem install thoughtbot-paperclip
sudo gem install gem install nifty-generators

Create our Rails app and generate a few things for us to play with.

rails demo
# remove index file from the public directory
 
script/generate nifty_layout
script/generate nifty_scaffold Album name:string
 
# config/routes.rb
map.root :controller => 'albums'
 
script/generate model Photo album_id:integer
script/generate paperclip photo data
rake db:migrate
script/server

Now open up the form partial in app/views/albums and modify it like so.

<% form_for @album, :html => { :multipart => true } do |f| %>
  <%= f.error_messages %>
  <p>
    <%= f.label :name %><br />
    <%= f.text_field :name %>
  </p>	
  <div id="photos">
     <% if @album.new_record? %>
       <%= render :partial => 'photo', :locals => { :form => f, :photo => @album.photos.build } %>
    <% end %>
  </div>
  <%= add_object_link("New Photo", f, @album.photos.build, "photo", "#photos") %>
  <p><%= f.submit "Submit" %></p>
<% end %>

Then add a new partial under app/views/albums called photo:

<div class="photo">
  <p>
  <% form.fields_for :photos, photo, :child_index => (photo.new_record? ? "index_to_replace_with_js" : nil) do |photo_form| %>    
    <%= photo_form.file_field :data %>
    <%= link_to_function "delete", "remove_field($(this), ('.photo'))" %><br/>
  <% end %>
  </p>
</div>

Add the necessary Javascript methods so that we can generate photo file upload fields.

# in application.js
function remove_field(element, item) {
  element.up(item).remove();
}
 
# download the jquery library to your javascripts folder and then modify application.html.erb layout file.
<head>
  <title><%= h(yield(:title) || "Untitled") %></title>
  <%= stylesheet_link_tag 'application' %>
  <%= javascript_include_tag :defaults, 'jquery-1.3.2.min' %>
  <script>
     jQuery.noConflict();
   </script>
   <%= yield(:head) %>
</head>

Add the following helper methods to albums_helper.rb. This bit was modified from apidock, rails, fields_for section.

  def add_object_link(name, form, object, partial, where)
    html = render(:partial => partial, :locals => { :form => form}, :object => object)
    link_to_function name, %{
      var new_object_id = new Date().getTime() ;
      var html = jQuery(#{js html}.replace(/index_to_replace_with_js/g, new_object_id)).hide();
      html.appendTo(jQuery("#{where}")).slideDown('slow');
    }
  end
 
  def js(data)
    if data.respond_to? :to_json
      data.to_json
    else
      data.inspect.to_json
    end
  end

Now add the following to your models.

# photo class
require 'paperclip'
 
class Photo < ActiveRecord::Base
  belongs_to :album
 
  has_attached_file :data, :styles => { :medium => "300x300>", :thumb => "100x100>" }
  validates_attachment_content_type :data, :content_type => 'image/jpeg', :message => "has to be in jpeg format"
end
 
# album class
class Album < ActiveRecord::Base
  attr_accessible :name
  validates_presence_of :name
  has_many :photos, :dependent => :destroy
 
  accepts_nested_attributes_for :photos
end

Notice the accepts_nested_attributes_for in the album class? That pretty much takes care of a plethora of code we would otherwise have to add to facilitate nested model behavior.

Now add this to your show.html.erb under apps/views/albums and you’re done.

<% title "Album" %>
 
<p>
  <strong>Name:</strong>
  <%=h @album.name %>
</p>
 
<% for photo in @album.photos %>
  <p><%= image_tag(photo.data(:thumb)) %></p>
<% end %>
 
<p>
  <%= link_to "Edit", edit_album_path(@album) %> |
  <%= link_to "Destroy", @album, :confirm => 'Are you sure?', :method => :delete %> |
  <%= link_to "View All", albums_path %>
</p>

That’s it folks. Enjoy and feel free to ask any questions or drop any comments!

, , ,

  1. #1 by Emerson - September 28th, 2009 at 19:08

    Man, I really appreciate you doing this. I’m going to take a look at this tutorial over the next few days.

    I’m currently building a site that uses:

    i. Restful Authentication Plugin for login system.
    ii. A “Profile” model that is now connected to the Restful Authentication “User” model.
    iii. Paperclip, that should allow multiple images that are associated with my Profile model (working on this right now).

    Anyways, thanks again, I’ll let you know how it goes!

  2. #2 by Millisami - September 30th, 2009 at 10:43

    Thanx for the article. Its great and I’ve implemented it.
    But there is one gotcha.
    When I press the Delete link, the following is the error in firebug console.

    element.up is not a function
    [Break on this error] element.up(item).remove();\n

    I tried with element.parent and also with element.prev
    But doesn’t work either??
    Have you any idea what is the problem??

  3. #3 by Ashwin - September 30th, 2009 at 19:46

    Hi man,
    This guide is really nice.I’m new to rails.I tried to implement this as in the procedure clearly mentioned.
    Could anybody clearly tell how to download jQuery library for javascripts and how to enable them in rails, I tried using
    aptitude install jquery & gem install jquery
    And when i run the server iam getting an error

    ArgumentError in Albums#new
    wrong number of arguments (0 for 1)
    In the line given below in app/views/albums/new.html.erb

    (photo.new_record? ? “index_to_replace_with_js” : nil) do |photo_form| %>

    Thanks and regards

  4. #4 by Dilloshion Raju - September 30th, 2009 at 20:22

    @ Millisami
    Hi make sure you have no conflicts between jquery and prototpe.

    <script>
         jQuery.noConflict();
     </script>
  5. #5 by Dilloshion Raju - September 30th, 2009 at 20:25

    @ Ashwin
    Hi just visit this link and then put the .js file into your public/javascripts folder. Then require it like i’ve shown in the example code in the main post.

    http://code.google.com/p/jqueryjs/downloads/detail?name=jquery-1.3.1.min.js

  6. #6 by Ashwin - October 1st, 2009 at 11:28

    @Dilloshion Raju
    Thanks Raju…
    I did download that .js file and used with javascripts as in the main post….
    But I still couldn’t solve that error which I was getting before.

    Regards for any help..

  7. #7 by Patrick Berkeley - October 3rd, 2009 at 21:48

    Great article! However, one of the main reasons to use jQuery is to make your JS unobtrusive, and your implementation is not.

  8. #8 by Millisami - October 8th, 2009 at 10:24

    @Dilloshion Raju
    Well, I am using just the jQuery. No prototype.

  9. #9 by Si - October 9th, 2009 at 09:23

    Thanks for this, I tried adding:

    validates_attachment_presence :data

    along with the other validation in Photo, validates_attachment_content_type
    and it didn’t work. Even if i selected a valid image, the validator rejected it.
    Have you tried this? come across this problem? or even fixed it?

    thanks.

(will not be published)
Subscribe to comments feed
  1. No trackbacks yet.

SetPageWidth