Uploading multiple images with paperclip
Firstly, I recommend not using the model name in the field name. Instead do file_etc. Also I recommend against using Attachment as your model name. I ran into this problem:
undefined method `quoted_table_name' for Paperclip::Attachment:Class
Also see, Advanced Rails Recipes, recipe #13 for help on this.
Ok let’s go.
Create the model.
1 | script/generate scaffold Photo file_file_name:string file_content_type:string file_file_size:integer |
Migrate the database in terminal
rake db:migrate
Add the following to app/model/photo.rb
1 | has_attached_file :file, :styles => { :medium => "300x300>", :thumb => "100x100>" } |
Aside: This assumes you are setting this up under the admin folder for app administration purposes.
Create admin folder with photos controller inside
script/generate controller admin::photos(If you get a warning like helpers/photos_helper.rb already exists then just delete that file, and run the generator again.)
Set up controllers/admin/photos_controller.rb to look something like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 | class Admin::PhotosController < ApplicationController layout 'admin' before_filter :login_required # GET /photos # GET /photos.xml def index @photos = Photo.find(:all) respond_to do |format| format.html # index.html.erb format.xml { render :xml => @photos } end end # GET /photos/1 # GET /photos/1.xml def show @photo = Photo.find(params[:id]) respond_to do |format| format.html # show.html.erb format.xml { render :xml => @photo } end end # GET /photos/new # GET /photos/new.xml def new @photo = Photo.new respond_to do |format| format.html # new.html.erb format.xml { render :xml => @photo } end end # GET /photos/1/edit def edit @photo = Photo.find(params[:id]) end # POST /photos # POST /photos.xml def create @photo = Photo.new(params[:photo]) respond_to do |format| if @photo.save flash[:notice] = 'Photo was successfully created.' format.html { redirect_to(admin_photos_path) } format.xml { render :xml => @photo, :status => :created, :location => @photo } else format.html { render :action => "new" } format.xml { render :xml => @photo.errors, :status => :unprocessable_entity } end end end # PUT /photos/1 # PUT /photos/1.xml def update @photo = Photo.find(params[:id]) respond_to do |format| if @photo.update_attributes(params[:photo]) flash[:notice] = 'Photo was successfully updated.' format.html { redirect_to(admin_photos_path) } format.xml { head :ok } else format.html { render :action => "edit" } format.xml { render :xml => @photo.errors, :status => :unprocessable_entity } end end end # DELETE /photos/1 # DELETE /photos/1.xml def destroy @photo = Photo.find(params[:id]) @photo.destroy respond_to do |format| format.html { redirect_to(admin_photos_url) } format.xml { head :ok } end end end |
Create the following view files under views/admin/photos/
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 | # _form.html.erb <%= f.error_messages %> <p> <%= f.label :file %><br /> <%= f.file_field :file %> </p> <p> <%= f.submit %> or <%= link_to 'Cancel', admin_photos_path %> </p> # edit.html.erb <h2>Editing photo</h2> <% form_for([:admin, @photo], :html => { :multipart => true }) do |f| %> <%= render :partial => 'form', :locals => { :f => f } %> <% end %> # index.html.erb <h2>Photos <small>(<%= link_to 'Create new photo', new_admin_photo_path %>)</small></h2> <table class="styledtable"> <tr> <th>Photo</th> <th>Sizes</th> <th>Manage</th> </tr> <% for photo in @photos %> <tr class="<%= cycle('even','odd') %>"> <td><%= image_tag(photo.file.url(:thumb)) %></td> <td><%= link_to 'thumb', photo.file.url(:thumb) %>, <%= link_to 'medium', photo.file.url(:medium) %>, <%= link_to 'original', photo.file.url(:original) %></td> <td><%= link_to 'edit', edit_admin_photo_path(photo) %> | <%= link_to 'delete', [:admin, photo], :confirm => 'Are you sure?', :method => :delete %></td> </tr> <% end %> </table> # new.html.erb <h2>New photo</h2> <% form_for([:admin, @photo], :html => { :multipart => true }) do |f| %> <%= render :partial => 'form', :locals => { :f => f } %> <% end %> # show.html.erb <p> <b>photo file name:</b> <%=h @photo.file_file_name %> </p> <p> <b>photo content type:</b> <%=h @photo.file_content_type %> </p> <p> <b>photo file size:</b> <%=h @photo.file_file_size %> </p> <%= link_to 'Back', admin_photos_path %> |
Edit your config/routes.rb file so that photos can route to apps/controllers/admin/
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | map.resources :photos map.resources :categories map.resources :manufacturers map.resources :users map.resource :session map.resources :line_items map.resources :orders map.resources :products map.namespace :admin do |admin| admin.resources :products admin.resources :orders admin.resources :manufacturers admin.resources :categories admin.resources :users admin.resource :session admin.resources :photos end |
Add paperclip’s plugin functionality to the model photo.rb
1 2 3 | class Photo < ActiveRecord::Base has_attached_file :file, :styles => { :medium => "300x300>", :thumb => "100x100>" } end |
Restart your server and test that you can add photos by going to http://localhost:3000/admin/photos/new
Did that work. Good. Now we can finally, move onto adding multiple images to another model. I am going to use the product.rb model and products_controller.rb to add these multiple images.
Add the belongs_to and has_many to the models.
1 2 | # app/models/product.rb has_many :photos |
1 2 3 4 5 | # app/models/photo.rb belongs_to :product # paperclip has_attached_file :file, :styles => { :medium => "300x300>", :thumb => "100x100>" } |
Add references to the photos model
script/generate migration add_product_references_to_photosAnd populate that file like so
1 2 3 4 5 6 7 8 9 10 11 12 13 | class AddProductReferencesToPhotos < ActiveRecord::Migration def self.up change_table :photos do |t| t.references :product end end def self.down change_table :photos do |t| t.remove_references :product end end end |
rake db:migrate
Then I am going to follow Railscast Episode 73: complex forms to add multiple images.
————————
UPDATE:
Use advanced Rails Recipes # 13
DON’T FORGET THE MULTIPART FOR UPLOADING IN YOUR FORM!!! Recipe #13 fails to mention this since it’s not dealing with images.
1 2 3 | <% form_for([:admin, @product], :html => { :multipart => true }) do |f| %> <%= render :partial => 'form', :locals => { :f => f } %> <% end %> |
————————
Add photos.build to new action of apps/controllers/admin/products_controller.rb
1 2 3 4 5 6 7 8 9 10 | # admin/products_controller.rb def new @product = Product.new 3.times { @product.photos.build } respond_to do |format| format.html # new.html.erb format.xml { render :xml => @product } end end |
Create _photo.html.erb partial under /app/views/admin/products/
1 2 3 4 5 6 7 | # _photo.html.erb <% fields_for "product[photo_attributes][]", photo do |photo_form| %> <p> <%= photo_form.label :file %><br /> <%= photo_form.file_field :file %> </p> <% end %> |
Add collection partial to /app/views/admin/products/_form.html.erb
1 | <%= render :partial => 'photo', :collection => @product.photos %> |
And that’s it. That will work.
Though there are still some unresolved issues. Take a look at Ryan’s Railscasts (except for episode 75. See Advanced Rails Recipe #13 instead.) on complex forms to add ajax, and also make sure you add dependent destroy to your model. Otherwise, when you delete a product the photos won’t be deleted.

Andrew Herbert | August 31, 2008
Great tutorial so far, but I’m getting one error when I try to edit a photo (before adding the photo to the products model). Any ideas on why this is popping up? Also, the label line is commented out because it was also throwing an error…
`@admin#’ is not allowed as an instance variable name
Extracted source (around line #5):
2:
3:
4:
5:
6:
Andrew Herbert | August 31, 2008
In my last post, it stripped out the text inside the gt and lt.
The error message should read
@admin#<Photo:0×26c9470> is not allowed as an instance variable name
Andrew Herbert | August 31, 2008
Ignore those last two posts, I figured out the solution. Following recipe 13 now. Thanks.