.:: Run Fat Boy .net ::.

Fitness for the buffet enthusiast.

Choose a Topic:

Clueless about getting into shape? Try RunFatBoy.

Top Posts:

Fri
16
May '08

Managing Multiple Images for Multimodel Forms

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.

Interface screenshot.

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
< %= 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.") + "&nbsp;Add Image" do |page|
    page.insert_html :bottom, :images, :partial => "product_image", :o 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

10 Responses to “Managing Multiple Images for Multimodel Forms”

  1. Jose Says:

    man…thank you so much for this…really

  2. Jose Says:

    I came with this error while trying to update a file.

    undefined method `uploaded_data=’ for #

    Any idea?

    thanks

  3. Jose Says:

    Great! it worked…thanks for this again…

  4. Jose Says:

    Got it to work and all, but now I’m having problems displaying a thumbnail for a given product. Like just getting the first image associated and displaying it’s thumbnail.

  5. Russ Jones Says:

    I would love to hear how you tackled validation with this approach? In my case I mimic your strategy pretty close but I put the “attachable” behavior into a module instead which I can reuse.

    http://pastie.textmate.org/private/l4h8gifviowysihp32q

    My only issue is I’m not able to show full error messages, and just that the association is invalid. IE, “Attachments is invalid”

  6. Dimi Says:

    Everything works fine, I just have a small problem, I my Photo model (Product_image) has :user_id, so I added in the

    but after executing the save_images my photo :user_id is null, does not update.

  7. Dimi Says:

    sorry, I meant that other Product_Image attributes are not updated

  8. Phuong Says:

    When you add new image in edit page, the last image will be replaced.

  9. SampsonPetra24 Says:

    I had got a desire to start my commerce, nevertheless I did not have enough amount of cash to do this. Thank God my close fellow advised to utilize the loan. So I received the sba loan and made real my dream.

  10. ClubPenguinCheats Says:

    I’m having problems displaying a thumbnail for a given product. Like just getting the first image associated and displaying it’s thumbnail.

Leave a Reply