Posts

has_many file attachments using Paperclip 16 Jun 2010
Tags: gem, paperclip, has_many, multiple file attachments

I decided to use paperclip instead of attachment_fu for our new app. This is just a procedure on how to use paperclip with a has_many association.

We create two models: post and post_photo

plain
>> ruby script/generate model post title:text body:text
>> ruby script/generate model post_photo post_id:integer

and associate them as follows.

ruby
class Post < ActiveRecord::Base
  has_many :post_photos
end

class PostPhoto < ActiveRecord::Base
  belongs_to :post
  has_atttached_file :photo
end

Using paperclip's generator we create a migration to add columns to post_photo

plain
>> ruby script/generate paperclip post_photo photo

After that, create your views and controller methods. (Btw, I use haml for the views. I also like creating an associated record using virtual attributes. I also used the prototype based js, Builder, to create dom elements.)

haml
# Post#new.html.haml
- form_for @post do |f|
  %label Title
  = f.text_field :post

  %label Body
  = f.text_area :body

  %label Photos
  = file_field_tag 'post[photo_attributes][]'

  #new_photos
  = link_to_function 'Add', 'add_file_field()'

:javascript
  function add_file_field() {
    var div = Builder.new('div');
    var file = Builder.new('input', {type:'file', name:'post[photo_attributes][]'});
    var link = Builder.new('a', {onclick:'$("this").up().remove();}, 'Remove');

    $('div').appendChild(file);
    $('div').appendChild(link);
    $('new_photos').appendChild(div);
ruby
# PostsController
def create
  @post = Post.new(params[:post])

  if @post.save
    flash[:notice] = 'Post saved'
    redirect_to @post
  else
    render :action => :new
  end
end

# post.rb
def photo_attributes=(attribs)
  attribs.each do |attrib|
    post_photos.build :photo => attrib
  end
end

multiple image upload with preview using ajax and jquery 04 Jul 2010
Tags: paperclip, preview, ajax, jquery, multiple image upload

In this tutorial, we will use jquery to upload multiple images using ajax and show a preview of the images. We'll also use the paperclip gem to store the files. You can look at how to add paperclip to your project .

First we generate the image model and set it to use paperclip. We'll also create a photo controller

plain
>> script/generate model photo
>> script/generate paperclip photo photo
>> rake db:migrate
>> script/generate controller photos
ruby
# app/models/photo.rb
class Photo < ActiveRecord::Base
  has_attached_file :photo
end

Next, we setup the view and javascript for ajaxupload.

Again, I'm using haml instead of erb templates, and builder for creating DOM elements.

And since we're using prototype and jquery in the same page, we need to make sure that there's no conflict between the two libraries. Add the following code in the head tag of your page.

ruby
= javascript_include_tag :defaults
= javascript_include_tag 'builder', 'jquery-1.4.2.min', 'ajaxupload'

:javascript
  var $j = jQuery.noConflict();

And here's the view

ruby
# app/views/photos/new.html.haml
- form_tag photos_path, :method => :post do
  #photos
  #file_upload
    %label Add a Photo
    = file_field_tag :photo, '', :id => 'file_tag'
    = image_tag '/images/spinner.gif', :id => 'spinner', :style => 'display:none'
    = submit_tag 'Save Photos'

:javascript
  $j(document).ready(function(){
    new AjaxUpload('file_tag', {
      action: '/photo_preview?authenticity_token=' + encodeURIComponent(#{form_authenticity_token.inspect}),
      name: 'image',
      onSubmit: function(file, extension) {
        $('spinner').show();
      },
      onComplete: function(file, response) {
        $('spinner').hide();
        show_preview(response);
      }
    });
  });

  function show_preview(path) {
    var parent = Builder.node('div', {class:'preview'});
    var image = Builder.node('img', {src:path, width:'80px'});
    var hidden = Builder.node('input', {type:'hidden', value:path, name:'photo[]'});

    parent.appendChild(image);
    parent.appendChild(hidden);
    $('photos').appendChild(parent);
  }

Now we need to create the photo_preview and create action in photos controller

ruby
# app/controllers/photos_controller.rb
# of course, this code won't work if the user uploads different photos with the same filename
def create
  photos = params[:paths].collect {|p| {:photo => File.new('public' + p)}}
  @photos = Photo.create(photos)
  # now do whatever you wish with @photos
end

def photo_preview
  if params[:image]
    filename = params[:image].original_filename
    path = '/images/' + filename
    f = File.new('public' + path, 'w') {|f| f.write params[:image].read}
    render :text => path
  end
end