So, I have this web app that I am creating for a friend that helps with managing inventory for eBay auctions.
When a user created a product, my intention was to allow them to upload as many images as they would like to be associated with that product. When they would go to edit the inventory item I would like for them to be able to delete any previously uploaded images and upload new images if they like.
Here’s what my interface looks like.

So I have a good idea that I will probably use attachment_fu to manage the uploading of images. But wait, things are slightly more complicated because I will have one model that manages the product attributes (Products) and then another model that manages the uploaded images (ProductImages).
As usual, Ryan Bates to the rescue with his Railscast “Complex Forms Part 3″. Much of the following code will be familiar if you’ve watched this Railscast; I am just going to demonstrate the usage of attachment_fu with a multi-model setup.
So, within the product new/edit form I have a div defined where my images will be displayed.
........
<p>
Wholesale Price<br />
< %= f.text_field :wholesale_price %>
</p>
<div id="images">
Images
< % @product.product_images.in_groups_of(3) do |group| %>
< %= render :partial => 'product_image', :collection => group %>
<p style="clear: both"/>
< % end %>
</div>
<p style="clear: both"/>
< %= link_to_function image_tag('add.png', :border => 0, :alt => "Add image.") + " Add Image" do |page|
page.insert_html :bottom, :images, :partial => "product_image",
bject => ProductImage.new
end %>
<p>
</p>
My product_image partial looks like this :
< % if product_image != nil %>
< % fields_for "product[image_attributes][]", product_image do |f| %>
< % if product_image.new_record? %>
<div class="newimage">
<p>
< %= f.file_field :uploaded_data, :class => "formField", :index => nil %>
< %= link_to_function "Remove", "$(this).up('.newimage').remove()" %>
</p>
</div>
< % else %>
<div class="image">
< %= image_tag(product_image.public_filename(:stamp), :border => 0) %>
< %= f.file_field :uploaded_data , :index => nil, :style => 'display:none' %>
< %= link_to_function image_tag('cancel.png', :border => 0, :alt => "Delete image."), "mark_for_destroy(this)" %>
< %= f.hidden_field :id, :index => nil %>
< %= f.hidden_field :should_destroy, :index => nil, :class => 'should_destroy' %>
</div>
< % end %>
< % end %>
< % end %>
Areas of interest : I’m using the fields_for form helper because our images are part of another model. There’s a set of empty brackets after the definition
product[image_attributes][]
to tell Rails that we’re passing an array (in this case multiple images).
The call to the mark_for_destroy method. This allows me to directly hide the display of the image and mark a form flag that tells the system to destroy it once the form has been submitted. This technique is outlined in Ryan’s railscast.
In application.js
function mark_for_destroy(element) {
$(element).next('.should_destroy').value = 1;
$(element).up('.image').hide();
}
I have a product_image model that manages the attachments using attachment_fu. It has an attribute should_destroy which allows an image to be marked for deletion from the form.
class ProductImage < ActiveRecord::Base
has_attachment :content_type => :image,
:storage => :file_system,
:max_size => 2000.kilobytes,
:resize_to => '320x200>',
:thumbnails => { :thumb => '150x150>', :stamp => '75x75>' },
:processor => :ImageScience
validates_as_attachment
attr_accessor :should_destroy
def should_destroy?
should_destroy.to_i == 1
end
end
For the products model, we want to setup a virtual attribute called image_attributes (remember the array of images that we defined in our form above?) that will handle the saving/deletion of product images.
class Product < ActiveRecord::Base
belongs_to :vendor
belongs_to :product_status
belongs_to :ebay_category
has_many :product_images, :dependent => :destroy
after_update :save_images
def image_attributes=(image_attributes)
image_attributes.each do |attributes|
if attributes[:id].blank?
product_images.build(attributes)
else
product_image = product_images.detect { |t| t.id == attributes[:id].to_i}
product_image.attributes = attributes
end
end
end
def save_images
product_images.each do |image|
if image.should_destroy?
image.destroy
else
image.save(false)
end
end
end
end
Leave a comment »