2
votes

So, I'm working on an app where users can upload and manage photos with a bunch of industry specific metadata attached to them.

The Photo model has all this metadata in it, and I'm using Paperclip to attach the actual image file to the model and store the images on Amazon S3.

The user interaction currently works like this:

  1. A user clicks "Add Photo" and is taken to the "New Photo" page where he is presented a form.
  2. The first thing on the form is a file chooser. The user selects a file.
  3. Beneath this are several different fields of metadata for the user to fill out, so the user fills these out.
  4. The user hits submit, the file uploads and a new Photo object is created, the user is redirected to a different page.

So, the obvious improvement that I'd like to make is for the photo to actually upload at the beginning of this process so that there isn't such a noticeable delay between hitting submit and being redirected to the next page. It would also be nice to be able to show the user a thumbnail preview of their photo once it's done uploading, so that they can see the photo they're putting in metadata about as they fill in the form.

I figure I could make that happen if I split the image file into its own model, but I'd then be referring to the images like so:

@photo.attachment.file.url instead of the simpler @photo.file.url that I use now. I'd rather not "nest it" more deeply than I have to.

Also, splitting it into two models raises the issue of managing orphans, which I currently don't have to deal with.

So my questions are:

  1. Is there a good way - preferably not using Flash - to create this asynchronous upload behavior without splitting into two models, OR --
  2. If I must split the metadata and the file into two models, is there a way to get Paperclip to treat an attachment as its own model so that I can access it using <modelname>.<paperclip_method> instead of <model_name>.<attachment_attribute>.<paperclip_method>?

I know that's a big question, so thank you very much in advance for your help!

5

5 Answers

1
votes

I'd recommend that you replace the new action with an edit action (you can auto-create a photo model when the user selects the action. This way, you can use an AJAX file upload (supported in some modern browsers) with a Flash fallback for doing the upload, and also edit the metadata. For doing the upload, try looking at plupload or uploadify.

1
votes

What's wrong with simply defining Photo#url like so?

class Photo
  def url(*args)
    attachment.url(*args)
  end
end

No need to get fancy here.

1
votes

After a week of experimentation I just thought I'd post what I finally did:

I did in fact split the photo into two models, because I ended up having to create empty records with any approach I tried. I found it was easier in the end to have two seperate models because:

  1. It made it easier to work within Rails convention (use the standard REST actions for the second model to handle asynchronous updates, rather than having to add several custom actions to the parent model).

  2. No matter which option I tried I ended up having orphan records as a possibility. I found it was easier to have a parent object which does not ever save unless valid (the Photo model in my case), and to have attachments which may be orphans. The attachments are never called directly anywhere in the app, so there's no risk of accidentally having an empty record being pulled up, and no need to set a default scope or something in order to only show "valid" photos. Cleaning up orphans is pretty easy, just do Attachment.where( :parent_id => nil ) and delete all those.

  3. I can maintain the previous code by simply delegating attachment to photo. See this answer on another question.

I hope this saves others some trouble down the road. If you need Ajax functionality with attachments it is best to make them their own model. Also, for adding ajax file upload to forms, check out https://github.com/formasfunction/remotipart. This saved my app.

1
votes

Just wondering, are you not setting the dependent clause on your association? For example,

Class Photo < ActiveRecord::Base
  :has_one :attachment, :dependent => :destroy
end

This will prevent orphan records, as anytime a destroy method is called on Photo, it'll first do Photo.attachment.destroy. It works with :has_many as well.

0
votes

Basically, you're splitting up the creation of the Photo object into two parts: uploading the photo, and adding the meta-data. In a non-AJAX site, this would be two separate steps on two separate pages.

What I would do is allow the AJAX upload to instantiate a Photo object, and save it (with the uploaded photo). I would then have the AJAX code return a token to the site that corresponds to a session variable to let the form know the record for the photo has already been created. When the user submits the rest of the form, if the token is present, it will know to populate the data on the already created photo object. If the token is not present, it will know it needs to create the Photo object from scratch.