Dynamic multiple image uploads with Ruby on Rails


Dynamic multiple image uploads with Ruby on Rails

23 February 2010

This tutorial explains how to create a simple web application that allows a user to upload one, or multiple images. It makes use of the paperclip plugin, Ajax using RJS and new multi-model form functionality that has arrived with Rails 2.3.3 in the form of accepts_nested_attributes_for.

Make sure your up to date with Rails, you will need at least 2.3.3

Lets start by making a new rails application

rails shop

Lets add some scaffolding to our new application ...

script/generate scaffold product name:string price:decimal

Next we create a model class that will hold our photos:

script/generate model photo product_id:integer

Now we need to install the paperclip plugin, you need to have git installed for this:

script/plugin install git://github.com/thoughtbot/paperclip.git

Run the Paperclip generator, this will create the appropriate migrations.

script/generate paperclip photo data

Then we just run the migrations:

rake db:migrate

Right, thats everything setup, now its time to delve into the code. First thing to do is setup the models. The photo model should look like this:

class Photo < ActiveRecord::Base
  belongs_to :product
  has_attached_file :data  
end

The first line sets up half the relationship between Photo and Product. The second line adds the paperclip attachment to our Photo model, there are a lot of optional settings that can be made here, but are beyond the scope of this tutorial, see the tutorial in the references for more information on paperclip. For now lets keep things simple.

The product model should look like this:

class Product < ActiveRecord::Base
  has_many :photos
  accepts_nested_attributes_for :photos, :allow_destroy => true
end

The first line specifies that we want one or more photos to be associated with our product. The second line is where the magic happens, this is new in rails 2.3.3, this specifies that photos is a nested attribute, this allows you to save attributes on associated 'Photo' records through the parent, 'Product'.

The View Code

All we have to do now is write the view code. In most scenarios, the number of images you want to add will be variable, therefore we will allow the user to dynamically add as many images as required. This kind of functionality means javascript, so lets add in the prototype libraries in products.html.erb.

<head>
  ....
  <%= javascript_include_tag :defaults %>
</head>

Firstly lets add the view code necessary to create a new photo, add the following to your new.html.erb file, within the form_for:

<%= add_photo(f) %>

And also add file upload support to the form, by adding :multipart => true to the form_for, as shown here:

<% form_for(@product, :html => {:multipart => true}) do |f| %>

Next we will add the implementation for the add_photo function. Go to products_helper.rb, and add the following funtion:

def add_photo(form_builder)
  link_to_function "add", :id  => "add_photo" do |page|
    form_builder.fields_for :photos, Photo.new, :child_index => 'NEW_RECORD' do |photo_form|
      html = render(:partial => 'photo', :locals => {:f => photo_form })
      page << "$('add_photo').insert({ before: '#{escape_javascript(html)}'.replace(/NEW_RECORD/g, new Date().getTime()) });"
    end
  end
end

This is a little bit complex, so lets go through it.

link_to_funtion is a helper found in the ActionView library, you provide it with some link text, and some JavaScript to execute when the link is clicked, it is important for this example that the link is given an id, you will see why shortly. We must build up a string to be executed within our link_to_funtion block, so we create a model scope for our new photo using form_for. Within this new scope we evaluate a partial(we have not written this yet) and store it locally, we then pass this evaluated partial into some prototype javascript that will insert the rendered HTML into the page. The point at which it is inserted is specified above as inserting 'before' the element with id add_photo, that is why the link was given an id, so that the HTML could be inserted before it.

Next we need to add the partial that was mentioned above. Create a new file in your app/views/products directory called _photo.html.erb (the underscore prefix is a Rails convention that specifies it as a partial), and paste in the following code:

<fieldset>
    <% if !f.object.new_record? %>
        <%= image_tag f.object.data.url %>
    <% end %>
    
    <%= f.file_field :data %>
    <%= delete_photo(f) %>
</fieldset>

We use a fieldset tag so we can group related items together, in this case its an image, a file upload input tag, and a delete link that runs some javascript. The fieldset allows that delete link to target the element that encompasses it. Next, we check if the object we are displaying is a new record, if its not, then we have an existing photo, so lets display it with an image_tag. The final two lines here provide a file upload input tag, and a call to a helper method that will create the delete link, lets have a look at that helper now.

def delete_photo(form_builder)
  if form_builder.object.new_record?
    link_to_function("Remove this Photo", "this.up('fieldset').remove()")
  else
    form_builder.hidden_field(:_delete) +
    link_to_function("Remove this Photo", "this.up('fieldset').hide(); $(this).previous().value = '1'")
  end
end

The first thing we do in delete_photo is to check if we are dealing with a new record or not, if it is, then we can simply delete the fieldset element we marked up previously. If, however, we have an actual photo, then it needs to be deleted from the database. Because we set :allow_destroy => true for accepts_nested_attributes_for, we can use the virtual attribute _delete in order to delete the child record.

update

In order to display all the photos for your particular product, add the following code to your show.html.erb file.

<% @product.photos.each do |photo| %>
  <%= image_tag photo.data.url %>
<% end %>

And that wraps it up, try it out, you are now able to add and remove multiple images dynamically!

References:


คำสำคัญ (Tags): #images#upload#multiple files
หมายเลขบันทึก: 517613เขียนเมื่อ 27 มกราคม 2013 21:40 น. ()แก้ไขเมื่อ 27 มกราคม 2013 21:40 น. ()สัญญาอนุญาต: ครีเอทีฟคอมมอนส์แบบ แสดงที่มา-ไม่ใช้เพื่อการค้า-ไม่ดัดแปลงจำนวนที่อ่านจำนวนที่อ่าน:


ความเห็น (0)

ไม่มีความเห็น

พบปัญหาการใช้งานกรุณาแจ้ง LINE ID @gotoknow
ClassStart
ระบบจัดการการเรียนการสอนผ่านอินเทอร์เน็ต
ทั้งเว็บทั้งแอปใช้งานฟรี
ClassStart Books
โครงการหนังสือจากคลาสสตาร์ท